story_branch 2.1.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7a8e6bc148fecde83a3f76f088507313660e6ab1cd4fb059d61285b380c583d
4
- data.tar.gz: c5a1c714d28331dd096ff89858af114055adf528ac63a33326b5ee6893c0cb4c
3
+ metadata.gz: 1c822a7c84f60daf75722fad9e100028aa8bec4e28b6f0a811135c511975c756
4
+ data.tar.gz: b44a6fe6aa0a18f9bae8ff0c240f99637fea1c2b519bdc645449b867529f253d
5
5
  SHA512:
6
- metadata.gz: f4f7c388a01ad1a9c164c0e21997422fe020416927afd164bfc68042fa958fb95f102909ffda013c26ab38f8d963e0de3d9894fa063413cd3469b3cdbccf76d5
7
- data.tar.gz: 164713630f809f65b4c703bd90cfba6007586837b903a869553c9f95d16ff3169f9b56821036674aca4bd6cac7e1617acf3e28c2425f53088aba60cbb238f7e1
6
+ metadata.gz: 06c7c3802616972f40da61237b8f7f44bac809ec8b77f19ecddd700fef1b65c7551db9fe0cb31bc8e17935ad8b87d086828ce7fd398d50c02d7eac73f7ab9f70
7
+ data.tar.gz: 9c59ac43012d9bf73d680b5736897b2c6b7fea6d6c196f801c7df2718adb82aa81c719a28b1b9ba2b8658de2df512a89a920acc3cb402984fd8259c34a93efea
data/.circleci/config.yml CHANGED
@@ -7,7 +7,7 @@ workflows:
7
7
  - test:
8
8
  matrix:
9
9
  parameters:
10
- ruby_version: ["ruby:2.6-buster", "ruby:2.7-buster", "ruby:3.0-buster"]
10
+ ruby_version: ["ruby:2.6", "ruby:2.7", "ruby:3.0"]
11
11
 
12
12
  filters:
13
13
  tags:
@@ -29,7 +29,7 @@ jobs:
29
29
  ruby_version:
30
30
  type: string
31
31
  docker:
32
- - image: circleci/<< parameters.ruby_version >>
32
+ - image: cimg/<< parameters.ruby_version >>
33
33
  working_directory: ~/repo
34
34
 
35
35
  steps:
@@ -45,6 +45,7 @@ jobs:
45
45
  - run:
46
46
  name: install dependencies
47
47
  command: |
48
+ rm Gemfile.lock
48
49
  gem install bundler
49
50
  bundle install --jobs=4 --retry=3 --path vendor/bundle
50
51
 
@@ -76,7 +77,7 @@ jobs:
76
77
 
77
78
  publish:
78
79
  docker:
79
- - image: circleci/ruby:3.0-buster
80
+ - image: cimg/ruby:3.0
80
81
  working_directory: ~/repo
81
82
 
82
83
  steps:
data/.gitignore CHANGED
@@ -4,4 +4,3 @@ story_branch-*.gem
4
4
  .byebug_history
5
5
  coverage
6
6
  tools/release*.*
7
- Gemfile.lock
data/Changelog.md CHANGED
@@ -1,3 +1,25 @@
1
+ # 2.3.0
2
+
3
+ Mon Aug 22 23:55:00 2022 +0800
4
+
5
+ - Moved Git::Wrapper to separate gem
6
+ - Try to read local file from both current path and git root's directory (#136)
7
+
8
+ # 2.2.1
9
+
10
+ Mon Aug 15 17:36:00 2022 +0800
11
+
12
+ - Fixed broken command story_branch open when using linear.app (#126)
13
+ - Fixed linear.app graphql query to filter team by key instead of name (#130)
14
+
15
+ # 2.2.0
16
+
17
+ Wed Aug 11 07:37:00 2022 +0800
18
+
19
+ - Add support for linear.app (#127)
20
+ - Adds dependency on story branch's graphql client
21
+ - Update README to match closer to current gem behavior
22
+
1
23
  # 2.1.0
2
24
  Wed Jul 20 18:30:00 2022 +0800
3
25
 
data/Gemfile.lock ADDED
@@ -0,0 +1,162 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ story_branch (2.3.0)
5
+ blanket_wrapper (~> 3.0, > 3.0)
6
+ damerau-levenshtein (~> 1.3, > 1.3)
7
+ httparty (> 0)
8
+ jira-ruby (> 1.7, < 3)
9
+ story_branch-git_wrapper (~> 0.0.3)
10
+ story_branch-graphql (~> 0.0.2)
11
+ thor (> 0.20, < 2)
12
+ tty-command (~> 0.8, > 0.8)
13
+ tty-config (~> 0.2, > 0.2)
14
+ tty-pager (~> 0.12, > 0.12)
15
+ tty-prompt (~> 0.18, > 0.18)
16
+ xdg (> 3.0, < 6)
17
+
18
+ GEM
19
+ remote: https://rubygems.org/
20
+ specs:
21
+ activesupport (7.0.3.1)
22
+ concurrent-ruby (~> 1.0, >= 1.0.2)
23
+ i18n (>= 1.6, < 2)
24
+ minitest (>= 5.1)
25
+ tzinfo (~> 2.0)
26
+ addressable (2.8.1)
27
+ public_suffix (>= 2.0.2, < 6.0)
28
+ ast (2.4.2)
29
+ atlassian-jwt (0.2.1)
30
+ jwt (~> 2.1)
31
+ blanket_wrapper (3.0.2)
32
+ httparty
33
+ recursive-open-struct
34
+ coderay (1.1.3)
35
+ concurrent-ruby (1.1.10)
36
+ damerau-levenshtein (1.3.3)
37
+ diff-lcs (1.5.0)
38
+ docile (1.4.0)
39
+ fakefs (1.8.0)
40
+ git (1.12.0)
41
+ addressable (~> 2.8)
42
+ rchardet (~> 1.8)
43
+ httparty (0.20.0)
44
+ mime-types (~> 3.0)
45
+ multi_xml (>= 0.5.2)
46
+ i18n (1.12.0)
47
+ concurrent-ruby (~> 1.0)
48
+ jira-ruby (2.2.0)
49
+ activesupport
50
+ atlassian-jwt
51
+ multipart-post
52
+ oauth (~> 0.5, >= 0.5.0)
53
+ json (2.6.2)
54
+ jwt (2.4.1)
55
+ method_source (1.0.0)
56
+ mime-types (3.4.1)
57
+ mime-types-data (~> 3.2015)
58
+ mime-types-data (3.2022.0105)
59
+ minitest (5.16.3)
60
+ multi_xml (0.6.0)
61
+ multipart-post (2.2.3)
62
+ oauth (0.5.10)
63
+ ostruct (0.5.5)
64
+ parallel (1.22.1)
65
+ parser (3.1.2.1)
66
+ ast (~> 2.4.1)
67
+ pastel (0.8.0)
68
+ tty-color (~> 0.5)
69
+ pry (0.14.1)
70
+ coderay (~> 1.1)
71
+ method_source (~> 1.0)
72
+ public_suffix (5.0.0)
73
+ rainbow (3.1.1)
74
+ rake (13.0.6)
75
+ rchardet (1.8.0)
76
+ recursive-open-struct (1.1.3)
77
+ regexp_parser (2.5.0)
78
+ rexml (3.2.5)
79
+ rspec (3.11.0)
80
+ rspec-core (~> 3.11.0)
81
+ rspec-expectations (~> 3.11.0)
82
+ rspec-mocks (~> 3.11.0)
83
+ rspec-core (3.11.0)
84
+ rspec-support (~> 3.11.0)
85
+ rspec-expectations (3.11.0)
86
+ diff-lcs (>= 1.2.0, < 2.0)
87
+ rspec-support (~> 3.11.0)
88
+ rspec-mocks (3.11.1)
89
+ diff-lcs (>= 1.2.0, < 2.0)
90
+ rspec-support (~> 3.11.0)
91
+ rspec-support (3.11.0)
92
+ rspec_junit_formatter (0.5.1)
93
+ rspec-core (>= 2, < 4, != 2.12.0)
94
+ rubocop (1.35.1)
95
+ json (~> 2.3)
96
+ parallel (~> 1.10)
97
+ parser (>= 3.1.2.1)
98
+ rainbow (>= 2.2.2, < 4.0)
99
+ regexp_parser (>= 1.8, < 3.0)
100
+ rexml (>= 3.2.5, < 4.0)
101
+ rubocop-ast (>= 1.20.1, < 2.0)
102
+ ruby-progressbar (~> 1.7)
103
+ unicode-display_width (>= 1.4.0, < 3.0)
104
+ rubocop-ast (1.21.0)
105
+ parser (>= 3.1.1.0)
106
+ ruby-progressbar (1.11.0)
107
+ simplecov (0.21.2)
108
+ docile (~> 1.1)
109
+ simplecov-html (~> 0.11)
110
+ simplecov_json_formatter (~> 0.1)
111
+ simplecov-html (0.12.3)
112
+ simplecov_json_formatter (0.1.4)
113
+ story_branch-git_wrapper (0.0.3)
114
+ story_branch-graphql (0.0.2)
115
+ httparty (> 0)
116
+ strings (0.2.1)
117
+ strings-ansi (~> 0.2)
118
+ unicode-display_width (>= 1.5, < 3.0)
119
+ unicode_utils (~> 1.4)
120
+ strings-ansi (0.2.0)
121
+ thor (1.2.1)
122
+ tty-color (0.6.0)
123
+ tty-command (0.10.1)
124
+ pastel (~> 0.8)
125
+ tty-config (0.6.0)
126
+ tty-cursor (0.7.1)
127
+ tty-pager (0.14.0)
128
+ strings (~> 0.2.0)
129
+ tty-screen (~> 0.8)
130
+ tty-prompt (0.23.1)
131
+ pastel (~> 0.8)
132
+ tty-reader (~> 0.8)
133
+ tty-reader (0.9.0)
134
+ tty-cursor (~> 0.7)
135
+ tty-screen (~> 0.8)
136
+ wisper (~> 2.0)
137
+ tty-screen (0.8.1)
138
+ tzinfo (2.0.5)
139
+ concurrent-ruby (~> 1.0)
140
+ unicode-display_width (2.2.0)
141
+ unicode_utils (1.4.0)
142
+ wisper (2.0.1)
143
+ xdg (4.5.0)
144
+
145
+ PLATFORMS
146
+ ruby
147
+
148
+ DEPENDENCIES
149
+ bundler (~> 2.1, > 2.1)
150
+ fakefs (> 0.14, < 2)
151
+ git (~> 1.5, > 1.5)
152
+ ostruct (~> 0.1, > 0.1)
153
+ pry (~> 0.11, > 0.11)
154
+ rake (>= 12.3.3, < 14)
155
+ rspec (~> 3, > 3)
156
+ rspec_junit_formatter (~> 0.4, > 0.4)
157
+ rubocop (~> 1.22)
158
+ simplecov (~> 0.16, > 0.16)
159
+ story_branch!
160
+
161
+ BUNDLED WITH
162
+ 2.1.4
data/README.md CHANGED
@@ -2,10 +2,32 @@
2
2
  [![CircleCI](https://circleci.com/gh/story-branch/story_branch/tree/master.svg?style=svg)](https://circleci.com/gh/story-branch/story_branch/tree/master)
3
3
  [![Maintainability](https://api.codeclimate.com/v1/badges/7dbd75908417656853d7/maintainability)](https://codeclimate.com/github/story-branch/story_branch/maintainability)
4
4
 
5
+ # Table of Contents
6
+
7
+ * [Installing](#installing)
8
+ * [Usage](#usage)
9
+ * [Commands available](#commands-available)
10
+ * [Commentary](#commentary)
11
+ * [Configuration](#configuration)
12
+ * [Configuring PivotalTracker](#configuring-pivotaltracker)
13
+ * [Configuring Github](#configuring-github)
14
+ * [Configuring JIRA](#configuring-jira)
15
+ * [Configuring LinearApp](#configuring-linearapp)
16
+ * [Available settings](#available-settings)
17
+ * [Issue placement](#issue-placement)
18
+ * [Branch username](#branch-username)
19
+ * [Finish tag](#finish-tag)
20
+ * [Creating a new branch following the naming convention](#creating-a-new-branch-following-the-naming-convention)
21
+ * [PivotalTracker specific commands](#pivotaltracker-specific-commands)
22
+ * [Migrating](#migrating)
23
+ * [Old configuration](#old-configuration)
24
+ * [Old commands](#old-commands)
25
+ * [Contributing](#contributing)
26
+
27
+
5
28
  # Story Branch
6
29
 
7
- Story branch is a CLI application that interacts with Pivotal Tracker, Github and Jira
8
- at the moment.
30
+ Story branch is a CLI application that interacts with Pivotal Tracker, Github, Jira and LinearApp.
9
31
 
10
32
  For all the trackers it supports creating local branches from the tickets or
11
33
  opening the ticket in your browser from the branch you're working on. In the future
@@ -46,25 +68,97 @@ Commands:
46
68
  story_branch version # story_branch gem version
47
69
  ```
48
70
 
49
- ## Settings
71
+ ## Commentary
72
+
73
+ `story_branch configure`: Step by step configuration of a new tracker for your project
74
+
75
+ ### Configuration
76
+
77
+ The configuration is split into two different files: a `.story_branch.yml` in the root folder
78
+ of the project where you're configuring the tool and a `.story_branch.yml` in user's home directory.
79
+
80
+ For the management of the home directory, story_branch relies on [XDG](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html)
81
+ specification, so if configured, it'll be installed under `~/.config` or whatever your machine
82
+ specifies.
83
+
84
+ The idea behind the two files is that the one in the root of the project should be committed to your
85
+ repository and defines basic tracker configuration settings to be shared across the contributors
86
+ to your repository. These configuration settings include the tracker type, project id in the tracker,
87
+ where you want the ticket number to be placed amongst others.
88
+
89
+ The file under your config directory is meant to be stored only locally as it will contain the api
90
+ keys needed for story branch to access your tracker. The story_branch file under your config directory
91
+ should not be published anywhere.
50
92
 
51
- Story branch has a command available that will help you creating the configurations
52
- for the projects, but essentially you'll be asked for the pivotal tracker project id and your api key.
93
+ #### Configuring PivotalTracker
94
+
95
+ When running the command `story_branch configure` you'll be asked 3 things:
96
+ 1. tracker - You should select Pivotal Tracker
97
+ 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`
98
+ 3. api key - this is your personal api key. You can get that from [your profile page](https://www.pivotaltracker.com/profile)
99
+
100
+ #### Configuring Github
101
+
102
+ When running the command `story_branch configure` you'll be asked 3 things:
103
+ 1. tracker - You should select Github
104
+ 2. project id - This is the github repository name in the format `<owner>/<repo_name>`. E.g. `story-branch/story_branch`.
105
+ 3. api key - this is your personal api token. You can create one under your [developer profile tokens page](https://github.com/settings/tokens)
106
+
107
+ #### Configuring JIRA
108
+
109
+ The configuration for JIRA is slightly more complex as the endpoint changes according
110
+ 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)
111
+ 1. tracker - You should select JIRA
112
+ 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>
113
+ 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
114
+ 4. API key that you should have gotten in the first description step
115
+ 5. username used for login in the JIRA usually. If you use google email authentication, the username should be your email
116
+
117
+ #### Configuring LinearApp
118
+
119
+ When running the command `story_branch configure` you'll be asked 3 things:
120
+ 1. tracker - You should select LinearApp
121
+ 2. project id - This should be your team's id.
122
+ 3. api key - this is your personal api token. You can create one under your [account API settings](https://linear.app/settings/api)
123
+
124
+ #### Available settings
125
+
126
+ ##### Issue placement
127
+
128
+ On your local config you can add a line with `issue_placement: <Beginning|End>`.
129
+ Based on this configuration, when running `story_branch create`, the ticket id will be
130
+ used as prefix or suffix on the branch name.
131
+
132
+ E.g.
133
+ `issue_placement: Beginning`
53
134
 
54
- ### Configuring the project id
135
+ `story_branch create` will create a branch in the format: `<issue_number>-<issue_title>`
55
136
 
56
- The project id you can get it easily from the url when viewing the project.
57
- This value will be stored in the local configuration file that will be committed
58
- to the working repository
137
+ While
59
138
 
60
- ### Configuring the api key
139
+ `issue_placement: End`
61
140
 
62
- The api key you can get it from your account settings.
63
- This value will be stored in your global configuration file that typically is
64
- not shared with your co-workers in the repository. This way, each user will
65
- be properly identified in the tracker
141
+ `story_branch create` will create a branch in the format: `<issue_title>-<issue_number>`
66
142
 
67
- ### Configuring the finish tag
143
+ ##### Branch username
144
+
145
+ In some cases your workflow requires you to have an identifier prefixing the branch name.
146
+ You can configure that by setting the configuration `branch_username` under your project's
147
+ name in the global `story_branch.yml` file (`defaults to: ~/.config/.story_branch.yml`)
148
+
149
+ E.g.
150
+
151
+ ```
152
+ story-branch/story_branch:
153
+ api_key: my_fantastic_api_key
154
+ branch_username: rui
155
+ ```
156
+
157
+ Doing so, when running `story_branch create`, it will create a branch in the format: `rui/<issue_number>-<issue_title>`
158
+
159
+ Naturally, the issue number will be placed based on the issue placement setting
160
+
161
+ ##### Finish tag
68
162
 
69
163
  On your local config you can add a line with `finish_tag: <Some random word>`.
70
164
  This tag will be used in the commit message when running `story_branch finish`.
@@ -75,28 +169,11 @@ E.g.
75
169
  `story_branch finish` will make a commit with the message
76
170
  `[Resolves #12313] story title`
77
171
 
172
+ ### Creating a new branch following the naming convention
78
173
 
79
- ### .story_branch files
80
-
81
- When configuring story branch, it will create two .story_branch.yml files: one in
82
- your home folder (`~/`) and one in your project's root (`./`).
83
- The one in your home folder will be used to store the different project's configurations
84
- such as which api key to use. This is done so you don't need to commit your
85
- api key to the repository but still be able to use different keys in case you
86
- have different accounts.
87
-
88
- The one in your project root will keep a reference to the project configuration.
89
- For now, this reference is the project id. This file can be safely committed to
90
- the repository and shared amongst your co-workers.
91
-
92
- ## Commentary
93
-
94
- `story_branch create`: Creates a git branch with automatic reference to a
95
- Pivotal Tracker Story. It will get started stories from your active
96
- project. You can enter text and press TAB to search for a story
97
- name, or TAB to show the full list. It will then suggest an editable
98
- branch name. When the branch is created the `story_id` will
99
- be appended to it.
174
+ `story_branch create`: Creates a git branch with automatic reference to a tracker ticket.
175
+ The tickets/stories that will be fetched will depend on the project type. Once you choose the
176
+ ticket to work on, a new branch will be created based on the ticket title and id.
100
177
 
101
178
  e.g. `my-story-name-1234567`
102
179
 
@@ -107,6 +184,8 @@ e.g: `[Finishes #1234567] My story name`
107
184
  You must stage all changes (or stash them) first. Note the commit will not
108
185
  be pushed. Note: You'll be able to bail out of the commit.
109
186
 
187
+ ### PivotalTracker specific commands
188
+
110
189
  `story_branch start`: Start a story in Pivotal Tracker from the terminal.
111
190
  It'll get all un-started stories in your current project. You can
112
191
  enter text and press TAB to search for a story name, or TAB to show
@@ -117,31 +196,6 @@ It'll get all started stories in your current project. You can
117
196
  enter text and press TAB to search for a story name, or TAB to show
118
197
  the full list.
119
198
 
120
- ## Configuring PivotalTracker
121
-
122
- When running the command `story_branch configure` you'll be asked 3 things:
123
- 1. tracker - You should select Pivotal Tracker
124
- 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`
125
- 3. api key - this is your personal api key. You can get that from [your profile page](https://www.pivotaltracker.com/profile)
126
-
127
- ## Configuring Github
128
-
129
- When running the command `story_branch configure` you'll be asked 3 things:
130
- 1. project id - This is the github repository name in the format `<owner>/<repo_name>`. E.g. `story-branch/story_branch`.
131
- 2. tracker - You should select Github
132
- 3. api key - this is your personal api token. You can create one under your
133
- [developer profile tokens page](https://github.com/settings/tokens)
134
-
135
- ## Configuring JIRA
136
-
137
- The configuration for JIRA is slightly more complex as the endpoint changes according
138
- 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)
139
- 1. tracker - You should select JIRA
140
- 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
141
- 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
142
- 4. API key that you should have gotten in the first description step
143
- 5. username used for login in the JIRA usually. If you use google email authentication, the username should be your email
144
-
145
199
  ## Migrating
146
200
 
147
201
  ### Old configuration
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative '../config_manager'
4
4
  require_relative '../command'
5
+ require_relative '../constants'
5
6
  require 'tty-config'
6
7
  require 'tty-prompt'
7
8
 
@@ -73,12 +74,7 @@ module StoryBranch
73
74
  def tracker
74
75
  return @tracker if @tracker
75
76
 
76
- trackers = {
77
- 'Pivotal Tracker' => 'pivotal-tracker',
78
- 'Github' => 'github',
79
- 'JIRA' => 'jira'
80
- }
81
- @tracker = prompt.select('Which tracker are you using?', trackers)
77
+ @tracker = prompt.select('Which tracker are you using?', StoryBranch::AVAILABLE_TRACKERS)
82
78
  end
83
79
  end
84
80
  end
@@ -3,6 +3,7 @@
3
3
  require 'tty-config'
4
4
  require 'tty-prompt'
5
5
  require 'xdg'
6
+ require 'story_branch/git'
6
7
 
7
8
  module StoryBranch
8
9
  # Config manager is used to manage all possible configuration settings
@@ -141,7 +142,7 @@ module StoryBranch
141
142
  end
142
143
 
143
144
  def load_configs
144
- @local = read_config('.')
145
+ @local = local_config
145
146
  xdg_conf = XDG::Config.new
146
147
  home_path = if conf_exist?(Dir.home)
147
148
  Dir.home
@@ -151,6 +152,11 @@ module StoryBranch
151
152
  @global = read_config(home_path)
152
153
  end
153
154
 
155
+ def local_config
156
+ possible_local_paths = [Dir.pwd, StoryBranch::Git::Wrapper.command('rev-parse --show-toplevel')].uniq
157
+ read_config(possible_local_paths)
158
+ end
159
+
154
160
  def read_config(path)
155
161
  config = init_config(path)
156
162
  config.read if config.persisted?
@@ -162,10 +168,13 @@ module StoryBranch
162
168
  config.persisted?
163
169
  end
164
170
 
165
- def init_config(path)
171
+ def init_config(paths)
172
+ paths = Array(paths)
166
173
  config = ::TTY::Config.new
167
174
  config.filename = CONFIG_FILENAME
168
- config.append_path path
175
+ paths.each do |path|
176
+ config.append_path path
177
+ end
169
178
  config
170
179
  end
171
180
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StoryBranch
4
+ AVAILABLE_TRACKERS = {
5
+ 'Pivotal Tracker' => 'pivotal-tracker',
6
+ 'Github' => 'github',
7
+ 'JIRA' => 'jira',
8
+ 'LinearApp' => 'linearapp'
9
+ }.freeze
10
+
11
+ TRACKERS_CLASSES = {
12
+ 'pivotal-tracker' => 'StoryBranch::Pivotal::Tracker',
13
+ 'github' => 'StoryBranch::Github::Tracker',
14
+ 'jira' => 'StoryBranch::Jira::Tracker',
15
+ 'linearapp' => 'StoryBranch::LinearApp::Tracker'
16
+ }.freeze
17
+ end
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'damerau-levenshtein'
4
- require_relative './git_wrapper'
4
+ require 'story_branch/git/wrapper'
5
5
 
6
6
  module StoryBranch
7
7
  # Class used to interact with git. It relies on git gem as the wrapper
8
8
  # and levenshtein algo to determine branch name proximity
9
9
  class GitUtils
10
10
  def self.similar_branch?(name)
11
- GitWrapper.branch_names.each do |n|
11
+ Git::Wrapper.branch_names.each do |n|
12
12
  return true if DamerauLevenshtein.distance(n, name) < 3
13
13
 
14
14
  branch_name_match = n.match(/(.*)(-[1-9]+[0-9]*$)/)
@@ -21,11 +21,11 @@ module StoryBranch
21
21
  end
22
22
 
23
23
  def self.branch_to_story_string(regex_matcher = /.*-(\d+$)/)
24
- GitWrapper.current_branch.match(regex_matcher)
24
+ Git::Wrapper.current_branch.match(regex_matcher)
25
25
  end
26
26
 
27
27
  def self.status?(state)
28
- status = GitWrapper.status
28
+ status = Git::Wrapper.status
29
29
  return false unless status
30
30
 
31
31
  !status[state].empty?
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StoryBranch
4
+ # Base class to represent an issue/ticket
5
+ class IssueBase
6
+ attr_reader :title, :id, :html_url
7
+
8
+ def initialize(tracker_issue, project = nil)
9
+ @project = project
10
+ @story = tracker_issue
11
+ end
12
+
13
+ def to_s
14
+ "#{@id} - #{@title}"
15
+ end
16
+
17
+ def dashed_title
18
+ StoryBranch::StringUtils.normalised_branch_name @title
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../issue_base'
4
+
5
+ module StoryBranch
6
+ module LinearApp
7
+ # LinearApp Issue representation
8
+ class Issue < StoryBranch::IssueBase
9
+ # NOTE: project here represents the team_id only
10
+ def initialize(tracker_issue, project)
11
+ super
12
+ @title = tracker_issue['title']
13
+ @id = "#{@project}-#{tracker_issue['number']}"
14
+ @html_url = tracker_issue['url']
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'issue'
4
+
5
+ module StoryBranch
6
+ module LinearApp
7
+ # LinearApp groups tickets in teams, so this is the team representation in
8
+ # story branch. It's equivalent to a project
9
+ class Team
10
+ def initialize(team_id, client)
11
+ @team_id = team_id
12
+ @client = client
13
+ end
14
+
15
+ def stories(_options = {})
16
+ response = @client.get(graphql_query: graphql_query)
17
+ stories_json = response.data['viewer']['assignedIssues']['nodes']
18
+ stories_json.map { |story| Issue.new(story, @team_id) }
19
+ rescue StoryBranch::Graphql::Error => e
20
+ raise "Error while querying for tickets:\n#{e.message}"
21
+ end
22
+
23
+ private
24
+
25
+ def graphql_query # rubocop:disable Metrics/MethodLength
26
+ %(
27
+ query Issue {
28
+ viewer {
29
+ assignedIssues (filter: { team: { key: { eq: "#{@team_id}"} } }) {
30
+ nodes {
31
+ id
32
+ title
33
+ description
34
+ number
35
+ url
36
+ }
37
+ }
38
+ }
39
+ }
40
+ ).squeeze(' ')
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'story_branch/graphql'
4
+ require_relative '../tracker_base'
5
+ require_relative 'team'
6
+
7
+ module StoryBranch
8
+ module LinearApp
9
+ # Linear App API wrapper for story branch tracker
10
+ class Tracker < StoryBranch::TrackerBase
11
+ API_URL = 'https://api.linear.app/'
12
+
13
+ def initialize(project_id:, api_key:, **)
14
+ super
15
+
16
+ # NOTE: project should be the representation of linear app team
17
+ @team_id = project_id
18
+ @api_key = api_key
19
+ @issue_regex = Regexp.new("#{@team_id}-(\\d+)")
20
+ end
21
+
22
+ def valid?
23
+ !@api_key.nil? && !@team_id.nil?
24
+ end
25
+
26
+ # TODO: This should probably be renamed to something more meaningful
27
+ # in the sense that it should be workable stories/issues
28
+ # which depend on the tracker's workflow. PivotalTracker they need to
29
+ # be started and estimated, while for Github they just need to be open
30
+ def stories
31
+ project.stories
32
+ end
33
+
34
+ def get_story_by_id(story_id)
35
+ project.stories(id: story_id).first
36
+ end
37
+
38
+ def client
39
+ @client ||= StoryBranch::Graphql::Client.new(api_url: API_URL, api_key: @api_key)
40
+ end
41
+
42
+ private
43
+
44
+ def configure_api
45
+ client
46
+ end
47
+
48
+ def project
49
+ return @project if @project
50
+ raise 'team must be set' unless @team_id
51
+
52
+ @project = Team.new(@team_id, client)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,12 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative './pivotal/tracker'
4
- require_relative './github/tracker'
5
- require_relative './jira/tracker'
3
+ require 'story_branch/git/wrapper'
6
4
  require_relative './git_utils'
7
- require_relative './git_wrapper'
8
5
  require_relative './config_manager'
9
6
  require_relative './url_opener'
7
+ require_relative 'tracker_initializer'
8
+
10
9
  require 'tty-prompt'
11
10
 
12
11
  module StoryBranch
@@ -20,7 +19,7 @@ module StoryBranch
20
19
  def initialize
21
20
  @config = ConfigManager.new
22
21
  abort(@config.errors.join("\n")) unless @config.valid?
23
- @tracker = initialize_tracker
22
+ @tracker = StoryBranch::TrackerInitializer.initialize_tracker(config: @config)
24
23
  abort('Invalid tracker configuration setting.') unless @tracker.valid?
25
24
  end
26
25
 
@@ -45,7 +44,7 @@ module StoryBranch
45
44
  commit_message = build_finish_message
46
45
  proceed = prompt.yes?("Commit with standard message? #{commit_message}")
47
46
  if proceed
48
- GitWrapper.commit commit_message
47
+ Git::Wrapper.commit commit_message
49
48
  else
50
49
  prompt.say 'Aborted'
51
50
  end
@@ -165,7 +164,7 @@ module StoryBranch
165
164
  feature_branch_name_with_story_id = build_branch_name(branch_name, story.id)
166
165
 
167
166
  prompt.say("Creating: #{feature_branch_name_with_story_id} with #{current_branch} as parent")
168
- GitWrapper.create_branch feature_branch_name_with_story_id
167
+ Git::Wrapper.create_branch feature_branch_name_with_story_id
169
168
  end
170
169
 
171
170
  def valid_branch_name(story)
@@ -207,21 +206,7 @@ module StoryBranch
207
206
  end
208
207
 
209
208
  def current_branch
210
- @current_branch ||= GitWrapper.current_branch
211
- end
212
-
213
- def initialize_tracker
214
- # TODO: Ideally this would be mapped out somewhere so we don't need to
215
- # evaluate anything from the config here
216
- tracker_type = @config.tracker_type
217
- case tracker_type
218
- when 'github'
219
- StoryBranch::Github::Tracker.new(**@config.tracker_params)
220
- when 'pivotal-tracker'
221
- StoryBranch::Pivotal::Tracker.new(**@config.tracker_params)
222
- when 'jira'
223
- StoryBranch::Jira::Tracker.new(**@config.tracker_params)
224
- end
209
+ @current_branch ||= Git::Wrapper.current_branch
225
210
  end
226
211
  end
227
212
  # rubocop:enable Metrics/ClassLength
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './constants'
4
+ require_relative './pivotal/tracker'
5
+ require_relative './github/tracker'
6
+ require_relative './jira/tracker'
7
+ require_relative './linear_app/tracker'
8
+
9
+ module StoryBranch
10
+ # helper class to find out which tracker should be initialized based on the
11
+ # configuration values
12
+ class TrackerInitializer
13
+ def self.initialize_tracker(config:)
14
+ tracker_class = find_tracker_class(config.tracker_type)
15
+ raise 'Invalid tracker configuration' unless tracker_class
16
+
17
+ tracker_class.new(**config.tracker_params)
18
+ end
19
+
20
+ def self.find_tracker_class(tracker_type)
21
+ tracker_str = StoryBranch::TRACKERS_CLASSES[tracker_type]
22
+ return nil unless tracker_str
23
+
24
+ Kernel.const_get(tracker_str)
25
+ end
26
+ end
27
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StoryBranch
4
- VERSION = '2.1.0'
4
+ VERSION = '2.3.0'
5
5
  end
data/story_branch.gemspec CHANGED
@@ -55,7 +55,10 @@ Gem::Specification.new do |spec|
55
55
 
56
56
  spec.add_runtime_dependency 'blanket_wrapper', '~> 3.0', '> 3.0'
57
57
  spec.add_runtime_dependency 'damerau-levenshtein', '~> 1.3', '> 1.3'
58
+ spec.add_runtime_dependency 'httparty', '>0'
58
59
  spec.add_runtime_dependency 'jira-ruby', '> 1.7', '< 3'
60
+ spec.add_runtime_dependency 'story_branch-graphql', '~> 0.0.2'
61
+ spec.add_runtime_dependency 'story_branch-git_wrapper', '~> 0.0.3'
59
62
  spec.add_runtime_dependency 'thor', '> 0.20', '< 2'
60
63
  spec.add_runtime_dependency 'tty-command', '~> 0.8', '> 0.8'
61
64
  spec.add_runtime_dependency 'tty-config', '~> 0.2', '> 0.2'
@@ -69,9 +72,9 @@ Gem::Specification.new do |spec|
69
72
  spec.add_development_dependency 'ostruct', '~> 0.1', '> 0.1'
70
73
  spec.add_development_dependency 'pry', '~> 0.11', '> 0.11'
71
74
  spec.add_development_dependency 'rake', '>= 12.3.3', '< 14'
72
- spec.add_development_dependency 'rubocop', '~> 1.22'
73
75
  spec.add_development_dependency 'rspec', '~> 3', '> 3'
74
76
  spec.add_development_dependency 'rspec_junit_formatter', '~> 0.4', '> 0.4'
77
+ spec.add_development_dependency 'rubocop', '~> 1.22'
75
78
  spec.add_development_dependency 'simplecov', '~> 0.16', '> 0.16'
76
79
  end
77
80
  # rubocop:enable Metrics/BlockLength
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../lib/story_branch/git_wrapper'
3
+ require 'story_branch/git/wrapper'
4
4
 
5
5
  def grab_and_print_log(from, to)
6
- all_log = StoryBranch::GitWrapper.command("log #{from}..#{to}")
6
+ all_log = StoryBranch::Git::Wrapper.command("log #{from}..#{to}")
7
7
 
8
8
  matches = all_log.scan(/CHANGELOG\n(.*?)--- 8< ---/m).flatten
9
9
  matches.map!(&:strip)
@@ -19,7 +19,7 @@ end
19
19
  # rubocop:disable Metrics/MethodLength
20
20
  # rubocop:disable Metrics/AbcSize
21
21
  def print_all_logs
22
- all_tags = StoryBranch::GitWrapper.command_lines('tag --list')
22
+ all_tags = StoryBranch::Git::Wrapper.command_lines('tag --list')
23
23
  cleanup_tags = all_tags.map do |t|
24
24
  { cleanup_tag: t.delete('v'), tag: t }
25
25
  end
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: 2.1.0
4
+ version: 2.3.0
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: 2022-07-20 00:00:00.000000000 Z
15
+ date: 2022-08-22 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: blanket_wrapper
@@ -54,6 +54,20 @@ dependencies:
54
54
  - - ">"
55
55
  - !ruby/object:Gem::Version
56
56
  version: '1.3'
57
+ - !ruby/object:Gem::Dependency
58
+ name: httparty
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">"
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ type: :runtime
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">"
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
57
71
  - !ruby/object:Gem::Dependency
58
72
  name: jira-ruby
59
73
  requirement: !ruby/object:Gem::Requirement
@@ -74,6 +88,34 @@ dependencies:
74
88
  - - "<"
75
89
  - !ruby/object:Gem::Version
76
90
  version: '3'
91
+ - !ruby/object:Gem::Dependency
92
+ name: story_branch-graphql
93
+ requirement: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: 0.0.2
98
+ type: :runtime
99
+ prerelease: false
100
+ version_requirements: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - "~>"
103
+ - !ruby/object:Gem::Version
104
+ version: 0.0.2
105
+ - !ruby/object:Gem::Dependency
106
+ name: story_branch-git_wrapper
107
+ requirement: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: 0.0.3
112
+ type: :runtime
113
+ prerelease: false
114
+ version_requirements: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: 0.0.3
77
119
  - !ruby/object:Gem::Dependency
78
120
  name: thor
79
121
  requirement: !ruby/object:Gem::Requirement
@@ -314,20 +356,6 @@ dependencies:
314
356
  - - "<"
315
357
  - !ruby/object:Gem::Version
316
358
  version: '14'
317
- - !ruby/object:Gem::Dependency
318
- name: rubocop
319
- requirement: !ruby/object:Gem::Requirement
320
- requirements:
321
- - - "~>"
322
- - !ruby/object:Gem::Version
323
- version: '1.22'
324
- type: :development
325
- prerelease: false
326
- version_requirements: !ruby/object:Gem::Requirement
327
- requirements:
328
- - - "~>"
329
- - !ruby/object:Gem::Version
330
- version: '1.22'
331
359
  - !ruby/object:Gem::Dependency
332
360
  name: rspec
333
361
  requirement: !ruby/object:Gem::Requirement
@@ -368,6 +396,20 @@ dependencies:
368
396
  - - ">"
369
397
  - !ruby/object:Gem::Version
370
398
  version: '0.4'
399
+ - !ruby/object:Gem::Dependency
400
+ name: rubocop
401
+ requirement: !ruby/object:Gem::Requirement
402
+ requirements:
403
+ - - "~>"
404
+ - !ruby/object:Gem::Version
405
+ version: '1.22'
406
+ type: :development
407
+ prerelease: false
408
+ version_requirements: !ruby/object:Gem::Requirement
409
+ requirements:
410
+ - - "~>"
411
+ - !ruby/object:Gem::Version
412
+ version: '1.22'
371
413
  - !ruby/object:Gem::Dependency
372
414
  name: simplecov
373
415
  requirement: !ruby/object:Gem::Requirement
@@ -420,6 +462,7 @@ files:
420
462
  - ".story_branch.yml"
421
463
  - Changelog.md
422
464
  - Gemfile
465
+ - Gemfile.lock
423
466
  - LICENSE.txt
424
467
  - README.md
425
468
  - Rakefile
@@ -441,16 +484,20 @@ files:
441
484
  - lib/story_branch/commands/start.rb
442
485
  - lib/story_branch/commands/unstart.rb
443
486
  - lib/story_branch/config_manager.rb
487
+ - lib/story_branch/constants.rb
444
488
  - lib/story_branch/git_utils.rb
445
- - lib/story_branch/git_wrapper.rb
446
489
  - lib/story_branch/github/issue.rb
447
490
  - lib/story_branch/github/label.rb
448
491
  - lib/story_branch/github/milestone.rb
449
492
  - lib/story_branch/github/project.rb
450
493
  - lib/story_branch/github/tracker.rb
494
+ - lib/story_branch/issue_base.rb
451
495
  - lib/story_branch/jira/issue.rb
452
496
  - lib/story_branch/jira/project.rb
453
497
  - lib/story_branch/jira/tracker.rb
498
+ - lib/story_branch/linear_app/issue.rb
499
+ - lib/story_branch/linear_app/team.rb
500
+ - lib/story_branch/linear_app/tracker.rb
454
501
  - lib/story_branch/main.rb
455
502
  - lib/story_branch/pivotal/project.rb
456
503
  - lib/story_branch/pivotal/story.rb
@@ -466,6 +513,7 @@ files:
466
513
  - lib/story_branch/templates/start/.gitkeep
467
514
  - lib/story_branch/templates/unstart/.gitkeep
468
515
  - lib/story_branch/tracker_base.rb
516
+ - lib/story_branch/tracker_initializer.rb
469
517
  - lib/story_branch/url_opener.rb
470
518
  - lib/story_branch/version.rb
471
519
  - story_branch.gemspec
@@ -496,7 +544,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
496
544
  - !ruby/object:Gem::Version
497
545
  version: '0'
498
546
  requirements: []
499
- rubygems_version: 3.2.32
547
+ rubygems_version: 3.1.2
500
548
  signing_key:
501
549
  specification_version: 4
502
550
  summary: Create git branches based on your preferred tracker tickets
@@ -1,108 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # NOTE: Consider extracting this to a separate gem
4
- module StoryBranch
5
- # GitWrapper to help running git commands with direct system calls
6
- # Essentially it provides a couple of commands to interact with git
7
- # - StoryBranch::GitWrapper.command('<cmd>', [<opts>])
8
- # Returns the output as is
9
- #
10
- # - StoryBranch::GitWrapper.command_lines('<cmd>', [<opts>])
11
- # Returns the output split into an array of lines, stripped and chomped
12
- #
13
- # - StoryBranch::GitWrapper.branch_names
14
- # Returns the list of available branch names, locally and configured remotes
15
- class GitWrapper
16
- STATI_MATCHERS = {
17
- modified_rx: /^ M (.*)/,
18
- untracked_rx: /^\?\? (.*)/,
19
- staged_rx: /^M (.*)/,
20
- added_rx: /^A (.*)/
21
- }.freeze
22
-
23
- def self.command(cmd, opts = [])
24
- gw = new
25
- gw.call(cmd, opts)
26
- end
27
-
28
- def self.command_lines(cmd, opts = [])
29
- result = command(cmd, opts)
30
- lines = result.split("\n")
31
- lines.each(&:strip!)
32
- end
33
-
34
- def self.branch_names
35
- # NOTE: Regex matcher for cases as:
36
- # remotes/origin/allow.... <- remote branch (remove 'remotes/origin')
37
- # * allow.... <- * indicates current branch (remove '* ')
38
- # allow <- local branch (do nothing)
39
- regex = %r{(^remotes/.*/|\s|[*])}
40
- all_branches.map do |line|
41
- line = line.sub(regex, '')
42
- line
43
- end
44
- end
45
-
46
- def self.current_branch
47
- current_branch_line = all_branches.detect do |line|
48
- line.match(/\*/)
49
- end
50
- current_branch_line.tr('*', ' ').strip
51
- end
52
-
53
- def self.all_branches
54
- command_lines('branch', '-a')
55
- end
56
-
57
- def self.create_branch(name)
58
- command('checkout', ['-b', name])
59
- end
60
-
61
- def self.status
62
- g_status = command_lines('status', '-s')
63
- return nil if g_status.empty?
64
-
65
- {
66
- modified: status_collect(g_status, STATI_MATCHERS[:modified_rx]),
67
- untracked: status_collect(g_status, STATI_MATCHERS[:untracked_rx]),
68
- added: status_collect(g_status, STATI_MATCHERS[:added_rx]),
69
- staged: status_collect(g_status, STATI_MATCHERS[:staged_rx])
70
- }
71
- end
72
-
73
- def self.status_collect(status, regex)
74
- chosen_stati = status.select { |e| e.match(regex) }
75
- chosen_stati.map { |e| e.match(regex)[1] }
76
- end
77
-
78
- def self.commit(message)
79
- command('commit', ['-m', message])
80
- end
81
-
82
- def initialize
83
- @system_git = 'git'
84
- end
85
-
86
- def call(cmd, opts = [])
87
- opts = prepare_opts(opts)
88
- git_cmd = "#{@system_git} #{cmd} #{opts}"
89
- `#{git_cmd}`.chomp.strip
90
- end
91
-
92
- private
93
-
94
- # NOTE: Taken from ruby git gem
95
- def escape(str = '')
96
- str = str.to_s
97
- return "'#{str.gsub('\'', '\'"\'"\'')}'" if RUBY_PLATFORM !~ /mingw|mswin/
98
-
99
- # Keeping the old escape format for windows users
100
- escaped = str.gsub('\'', '\'\\\'\'')
101
- %("#{escaped}")
102
- end
103
-
104
- def prepare_opts(opts = [])
105
- [opts].flatten.map { |s| escape(s) }.join(' ')
106
- end
107
- end
108
- end