ginjo-omniauth-slack 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/CHANGELOG.md +56 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +316 -0
- data/Rakefile +8 -0
- data/lib/omniauth/strategies/slack.rb +394 -0
- data/lib/omniauth-slack/version.rb +5 -0
- data/lib/omniauth-slack.rb +2 -0
- data/omniauth-slack.gemspec +25 -0
- data/test/helper.rb +57 -0
- data/test/support/shared_examples.rb +85 -0
- data/test/test.rb +246 -0
- metadata +138 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7429f940e8b8ab65aa6cf7b8afc127402c3f02256aa804ea426bdbeb46c1f2da
|
4
|
+
data.tar.gz: f15be0efa8af3bffafdb6c2c839c2a013a8488024a9c3d46441d6aea8320d05e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c5513ec8a12e7efb9eca056b8923fb168efee4c2e9af3753d5c18c67d85171bf6eca03b918326f464aa887628430670c08f7253c5c4d1e5b6fe14609dedfd394
|
7
|
+
data.tar.gz: 860aee4675bd03ba0825b623e978e96c16330c17017e77f646f44afe79f6427cb074e1096bc329754be0e5919badb2609128a7459fa687a660cab5d23e938dcc
|
data/.gitignore
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
.idea/
|
19
|
+
.ruby-version
|
20
|
+
.ruby-gemset
|
21
|
+
.gitchangelog.rc
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
## v2.4.0
|
2
|
+
|
3
|
+
Initial release of ginjo-omniauth-slack
|
4
|
+
|
5
|
+
* Add/fix AuthHash `extra['scopes_requested']`. [wbr]
|
6
|
+
|
7
|
+
* Refactor building of AuthHash to dynamically call additional API requests if necessary, scope permitting. [wbr]
|
8
|
+
|
9
|
+
* Add potential `bots.info` call, if bots info is not included in authorization response. [wbr]
|
10
|
+
|
11
|
+
* Add option to preload all possible user/team/bot data, using threads, after initial authorization token is received. [wbr]
|
12
|
+
|
13
|
+
* Use a method-specific mutex/semaphore for each API-call method when utilizing threads. [wbr]
|
14
|
+
|
15
|
+
* Add options to include or exclude specific API calls. [wbr]
|
16
|
+
|
17
|
+
* Ensure jruby compatibility. [wbr]
|
18
|
+
|
19
|
+
* Initial support for `additional_data` option, allowing additional API calls during callback phase. [wbr]
|
20
|
+
|
21
|
+
* Add support for `redirect_uri` option (covers PR https://github.com/kmrshntr/omniauth-slack/pull/39). [wbr]
|
22
|
+
|
23
|
+
* Initial support for Workspace apps and tokens. [wbr]
|
24
|
+
|
25
|
+
* Support setting slack subdomain at runtime with `team_domain` option. [wbr]
|
26
|
+
|
27
|
+
* Add token scopes to AuthHash `credentials` hash. [wbr]
|
28
|
+
|
29
|
+
* Append AuthHash `extra['raw_info']` section with full response of all API requests. [wbr]
|
30
|
+
|
31
|
+
* Respect `skip_info` option. [wbr]
|
32
|
+
|
33
|
+
* Don't insert `NA` in empty AuthHash fields, leave them as nil. [wbr]
|
34
|
+
|
35
|
+
* Add test coverage for new functionality. [wbr]
|
36
|
+
|
37
|
+
|
38
|
+
### Additional changes logged between 2.2.0 release and ginjo fork/refactor
|
39
|
+
|
40
|
+
The specifics of these commits may or may not be relevant in the ginjo fork, but their functionality is covered in one way or another.
|
41
|
+
|
42
|
+
* Merge pull request #51 from vadim7j7/fix-redirect_uri. [Shintaro Kimura]
|
43
|
+
|
44
|
+
* Merge pull request #50 from jonhue/master. [Shintaro Kimura]
|
45
|
+
|
46
|
+
* Rubygems via SSL. [jonhue]
|
47
|
+
|
48
|
+
* Fix #46 - Bump dependency version. [jonhue]
|
49
|
+
|
50
|
+
* Merge pull request #48 from jonhue/master. [Shintaro Kimura]
|
51
|
+
|
52
|
+
* Merge pull request #44 from pwnall/expose_identity. [Shintaro Kimura]
|
53
|
+
|
54
|
+
* Expose users.identity information in omniauth_hash.extra. [Victor Costan]
|
55
|
+
|
56
|
+
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 kimura
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,316 @@
|
|
1
|
+
⚠ **_WARNING_**: You are viewing the README of the ginjo fork of omniauth-slack.
|
2
|
+
|
3
|
+
To view the original omniauth-slack from [@kmrshntr](https://github.com/kmrshntr), go [here](https://github.com/kmrshntr/omniauth-slack).
|
4
|
+
|
5
|
+
# OmniAuth::Slack
|
6
|
+
|
7
|
+
This Gem contains the Slack strategy for OmniAuth and supports most features of
|
8
|
+
the [Slack OAuth2 authorization API](https://api.slack.com/docs/oauth), including both the
|
9
|
+
[Sign in with Slack](https://api.slack.com/docs/sign-in-with-slack) and
|
10
|
+
[Add to Slack](https://api.slack.com/docs/slack-button) approval flows.
|
11
|
+
|
12
|
+
This Gem supports Slack "classic" apps and tokens as well as the developer preview of [Workspace apps and tokens](https://api.slack.com/workspace-apps-preview).
|
13
|
+
|
14
|
+
|
15
|
+
## Before You Begin
|
16
|
+
|
17
|
+
You should have already installed OmniAuth into your app; if not, read the [OmniAuth README](https://github.com/intridea/omniauth) to get started.
|
18
|
+
|
19
|
+
Now sign into the [Slack application dashboard](https://api.slack.com/applications) and create an application. Take note of your API keys.
|
20
|
+
|
21
|
+
|
22
|
+
## Using This Strategy
|
23
|
+
|
24
|
+
First start by adding this gem to your Gemfile:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
gem 'ginjo-omniauth-slack'
|
28
|
+
```
|
29
|
+
|
30
|
+
Or specify the latest HEAD version from the ginjo repository:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
gem 'ginjo-omniauth-slack', git: 'https://github.com/ginjo/omniauth-slack'
|
34
|
+
```
|
35
|
+
|
36
|
+
Next, tell OmniAuth about this provider. For a Rails app, your `config/initializers/omniauth.rb` file should look like this:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
40
|
+
provider :slack, 'API_KEY', 'API_SECRET', scope: 'string-of-scopes'
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
Replace `'API_KEY'` and `'API_SECRET'` with the appropriate values you obtained [earlier](https://api.slack.com/applications).
|
45
|
+
Replace `'string-of-scopes'` with a comma (or space) separated string of Slack API scopes.
|
46
|
+
|
47
|
+
|
48
|
+
For a [Sinatra](http://sinatrarb.com/) app:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
require 'sinatra'
|
52
|
+
require 'omniauth-slack'
|
53
|
+
|
54
|
+
use OmniAuth::Builder do |env|
|
55
|
+
provider :slack,
|
56
|
+
ENV['SLACK_OAUTH_KEY_WS'],
|
57
|
+
ENV['SLACK_OAUTH_SECRET_WS'],
|
58
|
+
scope: 'string-of-scopes'
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
|
63
|
+
If you are using [Devise](https://github.com/plataformatec/devise) then it will look like this:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
Devise.setup do |config|
|
67
|
+
# other stuff...
|
68
|
+
|
69
|
+
config.omniauth :slack, ENV['SLACK_APP_ID'], ENV['SLACK_APP_SECRET'], scope: 'string-of-scopes'
|
70
|
+
|
71
|
+
# other stuff...
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
|
76
|
+
To manually install and require the gem:
|
77
|
+
```bash
|
78
|
+
# shell
|
79
|
+
gem install ginjo-omniauth-slack
|
80
|
+
````
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
# ruby
|
84
|
+
require 'omniauth-slack'
|
85
|
+
```
|
86
|
+
|
87
|
+
|
88
|
+
## Scopes
|
89
|
+
Slack lets you choose from a [few different scopes](https://api.slack.com/docs/oauth-scopes#scopes).
|
90
|
+
*Here's another [table of Slack scopes](https://api.slack.com/scopes) showing classic and new app compatibility.*
|
91
|
+
|
92
|
+
**Important:** You cannot request both `identity` scopes and regular scopes in a single authorization request.
|
93
|
+
|
94
|
+
If you need to combine "Add to Slack" scopes with those used for "Sign in with Slack", you should configure two providers:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
provider :slack, 'API_KEY', 'API_SECRET', scope: 'identity.basic', name: :sign_in_with_slack
|
98
|
+
provider :slack, 'API_KEY', 'API_SECRET', scope: 'team:read,users:read,identify,bot'
|
99
|
+
```
|
100
|
+
|
101
|
+
Use the first provider to sign users in and the second to add the application, and deeper capabilities, to their team.
|
102
|
+
|
103
|
+
Sign-in-with-Slack handles quick authorization of users with minimally scoped requests.
|
104
|
+
Deeper scope authorizations are acquired with further passes through the Add-to-Slack authorization process.
|
105
|
+
|
106
|
+
This works because Slack scopes are additive: Once you successfully authorize a scope, the token will possess that scope forever, regardless of what flow or scopes are requested at future authorizations.
|
107
|
+
|
108
|
+
Removal of scope requires revocation of the token.
|
109
|
+
|
110
|
+
|
111
|
+
## Authentication Options
|
112
|
+
|
113
|
+
Authentication options are specified in the provider block, as shown above, and all are optional except for `:scope`.
|
114
|
+
You will need to specify at least one scope to get a successful authentication and authorization.
|
115
|
+
|
116
|
+
Some of these options can also be given at runtime in the authorization request url.
|
117
|
+
|
118
|
+
`scope`, `team`, `team_domain`, and `redirect_uri` can be given at runtime. `scope`, `team`, and `redirect_uri` will be passed directly through to Slack in the OAuth GET request:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
https://slack.com/oauth/authorize?scope=identity.basic,identity.email&team=team-id&redirect_uri=https://different.subdomain/different/callback/path
|
122
|
+
```
|
123
|
+
|
124
|
+
`team_domain` will be inserted into the GET request as a subdomain `https://team-domain.slack.com/oauth/authorize`.
|
125
|
+
|
126
|
+
More information on provider and authentication options can be found in omniauth-slack's supporting gems [omniauth](https://github.com/omniauth/omniauth), [oauth2](https://github.com/oauth-xx/oauth2), and [omniauth-oauth2](https://github.com/omniauth/omniauth-oauth2).
|
127
|
+
|
128
|
+
|
129
|
+
### Scope
|
130
|
+
*required*
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
:scope => 'string-of-comma-or-space-separated-scopes'
|
134
|
+
```
|
135
|
+
|
136
|
+
Specify the scopes you would like to add to the token during this authorization request.
|
137
|
+
|
138
|
+
|
139
|
+
### Team
|
140
|
+
*optional*
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
:team => 'team-id'
|
144
|
+
# and/or
|
145
|
+
:team_domain => 'team-subdomain'
|
146
|
+
```
|
147
|
+
|
148
|
+
> If you don't pass a team param, the user will be allowed to choose which team they are authenticating against. Passing this param ensures the user will auth against an account on that particular team.
|
149
|
+
|
150
|
+
If you need to ensure that the users use the team whose team_id is 'XXXXXXXX', you can do so by passing `:team` option in your `config/initializers/omniauth.rb` like this:
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
Rails.application.config.middleware.use OmniAuth::Builder do
|
154
|
+
provider :slack, 'API_KEY', 'API_SECRET', scope: 'identify,read,post', team: 'XXXXXXXX'
|
155
|
+
end
|
156
|
+
```
|
157
|
+
|
158
|
+
If your user is not already signed in to the Slack team that you specify, they will be asked to provide the team domain first.
|
159
|
+
|
160
|
+
Another (possibly undocumented) way to specify team is by passing in the `:team_domain` parameter.
|
161
|
+
In contrast to setting `:team`, setting `:team_domain` will force authentication against the specified team (credentials permitting of course), even if the user is not signed in to that team.
|
162
|
+
However, if you are already signed in to that team, specifying the `:team_domain` alone will not let you skip the Slack authorization dialog, as is possible when you specify `:team`.
|
163
|
+
|
164
|
+
Sign in behavior with team settings and signed in state can be confusing. Here is a breakdown based on Slack documentation and observations while using this gem.
|
165
|
+
|
166
|
+
|
167
|
+
#### Team settings and sign in state vs Slack OAuth behavior.
|
168
|
+
|
169
|
+
| Setting and state | Will authenticate against specific team | Will skip authorization approval<br>-<br>*The elusive unobtrusive<br>[Sign in with Slack](https://api.slack.com/docs/sign-in-with-slack)* |
|
170
|
+
| --- | :---: | :---: |
|
171
|
+
| using `:team`, already signed in | :heavy_check_mark: | :heavy_check_mark: |
|
172
|
+
| using `:team`, not signed in | | :heavy_check_mark: |
|
173
|
+
| using `:team_domain`, already signed in | :heavy_check_mark: | |
|
174
|
+
| using `:team_domain`, not signed in | :heavy_check_mark: | |
|
175
|
+
| using `:team` and `:team_domain`, already signed in | :heavy_check_mark: | :heavy_check_mark: |
|
176
|
+
| using `:team` and `:team_domain`, not signed in | | :heavy_check_mark: |
|
177
|
+
| using no team parameters | | |
|
178
|
+
|
179
|
+
*Slack's authorization process will only skip the authorization approval step, if in addition to the above settings and state, ALL of the following conditions are met:*
|
180
|
+
|
181
|
+
* Token has at least one identity scope previously approved.
|
182
|
+
* Current authorization is requesting at least one identity scope.
|
183
|
+
* Current authorization is not requesting any scopes that the token does not already have.
|
184
|
+
* Current authorization is not requesting any non-identity scopes (but it's ok if the token already has non-identity scopes).
|
185
|
+
|
186
|
+
|
187
|
+
### Redirect URI
|
188
|
+
*optional*
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
:redirect_uri => 'https://<defaults-to-the-app-origin-host-and-port>/auth/slack/callback'
|
192
|
+
```
|
193
|
+
|
194
|
+
*This setting overrides the `:callback_path` setting.*
|
195
|
+
|
196
|
+
Set a custom redirect URI in your app, where Slack will redirect-to with an authorization code.
|
197
|
+
The redirect URI, whether default or custom, MUST match a registered redirect URI in [your app settings on api.slack.com](https://api.slack.com/apps).
|
198
|
+
See the [Slack OAuth docs](https://api.slack.com/docs/oauth) for more details on Redirect URI registration and matching rules.
|
199
|
+
|
200
|
+
|
201
|
+
### Callback Path
|
202
|
+
*optional*
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
:callback_path => '/auth/slack/callback'
|
206
|
+
```
|
207
|
+
|
208
|
+
*This setting is ignored if `:redirect_uri` is set.*
|
209
|
+
|
210
|
+
Set a custom callback path (path only, not the full URI) for Slack to redirect-to with an authorization code. This will be appended to the default redirect URI only. If you wish to specify a custom redirect URI with a custom callback path, just include both in the `:redirect_uri` setting.
|
211
|
+
|
212
|
+
|
213
|
+
### Skip Info
|
214
|
+
*optional*
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
:skip_info => false
|
218
|
+
```
|
219
|
+
|
220
|
+
Skip building the `InfoHash` section of the `AuthHash` object.
|
221
|
+
|
222
|
+
If set, only a single api request will be made for each authorization. The response of that authorization request may or may not contain user and email data.
|
223
|
+
|
224
|
+
|
225
|
+
### Include/Exclude Data
|
226
|
+
*optional*
|
227
|
+
|
228
|
+
```ruby
|
229
|
+
:include_data => %w(identity user_info user_profile)
|
230
|
+
|
231
|
+
# or
|
232
|
+
|
233
|
+
:exclude_data => %w(user_info team_info bot_info)
|
234
|
+
```
|
235
|
+
*These options are ignored if `:skip_info => true` is set.*
|
236
|
+
|
237
|
+
Specify which API calls to include or exclude after the initial authorization call.
|
238
|
+
This will affect what data you see in the AuthHash object. These two options are mutually exclusive. Use one or the other but not both. If neither option is declared, all API calls will be made (depending on scope and permissions).
|
239
|
+
|
240
|
+
The currently available calls are:
|
241
|
+
|
242
|
+
* identity
|
243
|
+
* user_info
|
244
|
+
* user_profile
|
245
|
+
* team_info
|
246
|
+
* bot_info
|
247
|
+
|
248
|
+
|
249
|
+
### Preload Data with Threads
|
250
|
+
*optional*
|
251
|
+
|
252
|
+
```ruby
|
253
|
+
:preload_data_with_threads => 0
|
254
|
+
```
|
255
|
+
*This option is ignored if `:skip_info => true` is set.*
|
256
|
+
|
257
|
+
With passed integer > 0, omniauth-slack preloads the basic identity-and-info API call responses, from Slack, using *<#integer>* pooled threads.
|
258
|
+
|
259
|
+
The default `0` skips this feature and only loads those API calls if required, scoped, and authorized, to build the AuthHash.
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
provider :slack, key, secret, :preload_data_with_threads => 2
|
263
|
+
```
|
264
|
+
|
265
|
+
More threads can potentially give a quicker callback phase.
|
266
|
+
The caveat is an increase in concurrent request load on Slack, possibly affecting rate limits.
|
267
|
+
|
268
|
+
|
269
|
+
### Additional Data
|
270
|
+
*experimental*
|
271
|
+
|
272
|
+
This experimental feature allows additional API calls to be made during the omniauth-slack callback phase.
|
273
|
+
Provide a hash of `{<name>: <proc-that-receives-env>}`, and the result will be attached as a hash
|
274
|
+
under `additional_data` in the `extra` section of the `AuthHash`.
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
provider :slack, key, secret,
|
278
|
+
additional_data: {
|
279
|
+
channels: proc{|env| env['omniauth.strategy'].access_token.get('/api/conversations.list').parsed['channels'] },
|
280
|
+
resources: proc{|env| env['omniauth.strategy'].access_token.get('/api/apps.permissions.resources.list').parsed }
|
281
|
+
}
|
282
|
+
```
|
283
|
+
|
284
|
+
|
285
|
+
## Workspace Apps and Tokens
|
286
|
+
This gem provides support for Slack's developer preview of [Workspace apps](https://api.slack.com/workspace-apps-preview). There are some important differences between Slack's classic apps and the new Workspace apps. The main points to be aware of when using omniauth-slack with Workspace Apps are:
|
287
|
+
|
288
|
+
* Workspace app tokens are issued as a single token per team. There are no user or bot tokens. All Workspace app API calls are made with the Workspace token. Calls that act on behalf of a user or bot are made with the same token.
|
289
|
+
|
290
|
+
* The available api calls and the scopes required to access them are different in Workspace apps. See Slack's docs on [Scopes](https://api.slack.com/scopes) and [Methods](https://api.slack.com/methods/workspace-tokens) for more details.
|
291
|
+
|
292
|
+
* The OmniAuth::AuthHash.credentials.scope object, returned from a successful authentication, is a hash with each value containing an array of scopes. See below for an example.
|
293
|
+
|
294
|
+
|
295
|
+
## Auth Hash
|
296
|
+
|
297
|
+
The AuthHash from this gem has the standard components of an `OmniAuth::AuthHash` object, with some additional data added to the `:info` and `:extra` sections.
|
298
|
+
|
299
|
+
If the scopes allow, additional api calls *may* be made to gather additional user and team info, unless the `:skip_info => true` is set.
|
300
|
+
|
301
|
+
The `:extra` section contains the parsed data from each of the api calls made during the authorization.
|
302
|
+
Also included is a `:raw_info` hash, which in turn contains the raw response object from each of the api calls.
|
303
|
+
|
304
|
+
The `:extra` section also contains `:scopes_requested`, which are the scopes requested during the current authorization.
|
305
|
+
|
306
|
+
See [this gist for an example AuthHash](https://gist.github.com/ginjo/3105cf4e975996c9032bb4725f949cd2) from a workspace token with a mix of identity scopes and regular app scopes applied.
|
307
|
+
|
308
|
+
See <https://github.com/omniauth/omniauth/wiki/Auth-Hash-Schema> for more info on the auth_hash schema.
|
309
|
+
|
310
|
+
## Contributing
|
311
|
+
|
312
|
+
1. Fork it
|
313
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
314
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
315
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
316
|
+
5. Create new Pull Request
|