abtion-aid 0.2.0 → 0.3.1

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: 8a1339e7c88c58cb0b3e6bd78f80b381433f50075466af838e62b53352ffec23
4
- data.tar.gz: 673ba5a521e3e86853c9acbeaa46d7dccf3000faea06fa47ce5321c0b82f4eca
3
+ metadata.gz: 5a777e8c76409c4bb71e1432027481e6887f6404b538ed81f324af12bcddd799
4
+ data.tar.gz: 16f94988582a60e12c00fed82c7b62f8a159bb4b4be61e8c4a6f5c8052ce222a
5
5
  SHA512:
6
- metadata.gz: eeaebd179113a9cb4ac6f52c1789b1f20286a428c6c0d1bb380322c91519e99663988e403ddb048fea646f923a4f6773560e30a014f69e3e61d0ecde11236701
7
- data.tar.gz: d35bb798022f67a5f91c550cd0b6b526a84c548992454061ae1a7e9b22ba3e4e902331b254dd95faa3c7266621670d9fa0fcf90e37f2a17c73e65c853e5df4fc
6
+ metadata.gz: 174b5404a30f59b4b789f76b7095dffc304251c84f38374a8eea6e2d21be21b4b48a3419364fdf27568e0ab86a39d01f46ab2ce5b3b2ab8be3bb348e8bfb95d8
7
+ data.tar.gz: f4bdc737181cf232024863952dfb11255dbee8df42eadd2b9fb20db445125551799f2456a15fbe2dd4e299110d39437ef6b7ab2ca0318cbe621435adc9435606
data/README.md CHANGED
@@ -34,7 +34,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
34
34
 
35
35
  ## Contributing
36
36
 
37
- Bug reports and pull requests are welcome on GitHub at https://github.com/dbalatero/aid.
37
+ Bug reports and pull requests are welcome on GitHub at https://github.com/abtion/aid.
38
38
 
39
39
  ## License
40
40
 
data/aid.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.authors = ['Abtion', 'David Balatero']
11
11
  spec.email = ['mail@abtion.com']
12
12
 
13
- spec.summary = 'A library to make repo scripts easy and discoverable.'
13
+ spec.summary = 'Abtion\'s development workflow tools'
14
14
  spec.homepage = 'https://github.com/abtion/aid'
15
15
  spec.license = 'MIT'
16
16
 
@@ -27,4 +27,8 @@ Gem::Specification.new do |spec|
27
27
  spec.add_development_dependency 'rake', '~> 10.0'
28
28
  spec.add_development_dependency 'rspec', '~> 3.0'
29
29
  spec.add_development_dependency 'rubocop'
30
+
31
+ spec.add_dependency 'http'
32
+ spec.add_dependency 'slack-notifier'
33
+ spec.add_dependency 'asana'
30
34
  end
@@ -0,0 +1,310 @@
1
+ require "asana"
2
+ require "tempfile"
3
+ require_relative "./shared/git_config"
4
+ require_relative "./shared/git_staged"
5
+
6
+ ASANA_URL_PATTERN = %r{https?://app.asana.com/\d+/(?<project_id>\d+)/(?<task_id>\d+)}.freeze
7
+
8
+
9
+ module Aid
10
+ module Scripts
11
+ class Begin < Aid::Script
12
+ include Aid::GitConfig
13
+ include Aid::GitStaged
14
+
15
+ def self.description
16
+ "Starts a new Asana story"
17
+ end
18
+
19
+ def self.help
20
+ <<~HELP
21
+ Fill me in.
22
+ aid begin:
23
+ Starts a story on Asana, moves it into the Work In Progress column,
24
+ and opens a pull request on GitHub for you.
25
+ Usage:
26
+ aid begin "https://app.asana.com/0/1135298954694367/1135903276459289/f"
27
+ HELP
28
+ end
29
+
30
+ def run
31
+ check_for_hub!
32
+ check_for_hub_credentials!
33
+
34
+ prompt_for_config!(
35
+ "asana.personal-access-token",
36
+ "Enter your personal access token",
37
+ <<~EOF
38
+ 1. Visit https://app.asana.com/0/developer-console
39
+ 2. Click "+ New access token" under "Personal access tokens"
40
+ 3. Give the token a name
41
+ 4. Copy the token and paste it back here.
42
+ EOF
43
+ )
44
+
45
+ prompt_for_config!(
46
+ "github.user",
47
+ "Enter your GitHub username",
48
+ <<~EOF
49
+ 1. Visit https://github.com and sign in
50
+ 2. Find the GitHub username you use and paste it in here
51
+ EOF
52
+ )
53
+
54
+ set_asana_project_id_if_needed!
55
+ check_for_existing_branch!
56
+ ensure_base_branch_is_ok!
57
+
58
+ step "Starting '#{asana_task.name}' on Asana" do
59
+ start_task
60
+ end
61
+
62
+ step "Creating local branch off '#{base_branch}'" do
63
+ check_for_staged_files!
64
+ reset_hard_to_base_branch!
65
+ create_git_branch!
66
+ make_empty_commit!
67
+ end
68
+
69
+ step "Pushing to Github" do
70
+ push_branch_to_github!
71
+ end
72
+
73
+ step "Opening pull request on Github" do
74
+ open_pull_request_on_github!
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def open_pull_request_on_github!
81
+ tempfile = Tempfile.new("begin_pull_request")
82
+
83
+ begin
84
+ tempfile.write(pull_request_description)
85
+ tempfile.close
86
+
87
+ labels = "WIP"
88
+
89
+ url = `hub pr list -h #{current_branch_name} -f '%U'`.strip
90
+ if url.empty?
91
+ url = `hub pull-request -F #{tempfile.path} -l "#{labels}" -d -a #{github_user}`.strip
92
+ end
93
+ puts colorize(:blue, url)
94
+
95
+ # Copy it to your clipboard
96
+ pbcopy_exist = system("command -v pbcopy >/dev/null 2>&1")
97
+ system("echo #{url.inspect} | pbcopy") if pbcopy_exist
98
+
99
+ # Update metadata in .git/config
100
+ result = url.match(%r{pull/(\d+)})
101
+ pull_request_id = result && result[1]
102
+
103
+ git_config("tasks.#{asana_task_id}.url", asana_url)
104
+ git_config("tasks.#{asana_task_id}.name", asana_task.name)
105
+ git_config("tasks.#{asana_task_id}.pull-request-id", pull_request_id)
106
+ ensure
107
+ tempfile.unlink
108
+ end
109
+ end
110
+
111
+ def github_user
112
+ git_config("github.user")
113
+ end
114
+
115
+ def push_branch_to_github!
116
+ silent "git push --force-with-lease -u origin '#{current_branch_name}'"
117
+ end
118
+
119
+ def make_empty_commit!
120
+ msg = "Initial commit for task ##{asana_task_id} [ci skip]"
121
+
122
+ silent("git commit --allow-empty -m #{msg.inspect}")
123
+ end
124
+
125
+ def current_branch_name
126
+ `git rev-parse --abbrev-ref HEAD`.strip
127
+ end
128
+
129
+ def ensure_base_branch_is_ok!
130
+ base_branch
131
+ end
132
+
133
+ def base_branch
134
+ @base_branch ||= prompt_for_base_branch
135
+ end
136
+
137
+ def prompt_for_base_branch
138
+ current_branch = current_branch_name
139
+
140
+ if current_branch != "master"
141
+ print colorize(
142
+ :yellow,
143
+ "Current branch is #{colorize(:blue, current_branch)} - do you want "\
144
+ "to create a branch off that? (y/n) > "
145
+ )
146
+
147
+ answer = STDIN.gets.strip
148
+
149
+ exit if answer[0] != "y"
150
+ end
151
+
152
+ current_branch
153
+ end
154
+
155
+ def story_branch_name
156
+ name = asana_task
157
+ .name
158
+ .gsub(%r{[/.]}, "-")
159
+ .gsub(/\s+/, "-")
160
+ .gsub(/[^\w-]/, "")
161
+ .downcase
162
+ .slice(0, 50)
163
+
164
+ "#{name}-#{asana_task_id}"
165
+ end
166
+
167
+ def start_task
168
+ # Assign task to runner of this script
169
+ user = asana.users.me
170
+ asana_task.update(assignee: user.gid)
171
+
172
+ # Move it to the WIP column
173
+ asana_task.add_project(
174
+ project: asana_project.gid,
175
+ section: asana_wip_section.gid
176
+ )
177
+ end
178
+
179
+ def asana_project
180
+ @asana_project ||= asana.projects.find_by_id(asana_project_id)
181
+ end
182
+
183
+ def asana_task
184
+ @asana_task ||= asana.tasks.find_by_id(asana_task_id)
185
+ end
186
+
187
+ def asana_sections
188
+ asana.sections.find_by_project(project: asana_project.gid)
189
+ end
190
+
191
+ def asana_wip_section
192
+ asana_sections.detect do |task|
193
+ task.name =~ /(Work In Progress|WIP)/i
194
+ end
195
+ end
196
+
197
+ def asana_project_id
198
+ git_config("asana.project-id")&.to_i
199
+ end
200
+
201
+ def set_asana_project_id_if_needed!
202
+ return if asana_project_id
203
+
204
+ result = asana_url
205
+ .match(ASANA_URL_PATTERN)
206
+
207
+ abort help unless result
208
+
209
+ id = result[:project_id].to_i
210
+
211
+ puts "Setting asana.project-id to #{id}"
212
+
213
+ git_config("asana.project-id", id)
214
+ end
215
+
216
+ def asana_task_id
217
+ @asana_task_id ||=
218
+ begin
219
+ result = asana_url
220
+ .match(ASANA_URL_PATTERN)
221
+
222
+ abort help unless result
223
+
224
+ result[:task_id].to_i
225
+ end
226
+ end
227
+
228
+ def asana_url
229
+ argv.first.to_s
230
+ end
231
+
232
+ def asana
233
+ @asana ||=
234
+ Asana::Client.new do |client|
235
+ client.default_headers "asana-enable" => "string_ids,new_sections"
236
+ client.authentication :access_token,
237
+ git_config("asana.personal-access-token")
238
+ end
239
+ end
240
+
241
+ def pull_request_description
242
+ <<~EOF
243
+ #{asana_task.name}
244
+
245
+ #{asana_url}
246
+ EOF
247
+ end
248
+
249
+ def reset_hard_to_base_branch!
250
+ silent "git checkout #{base_branch}",
251
+ "git fetch origin",
252
+ "git reset --hard origin/#{base_branch}"
253
+ end
254
+
255
+ def check_for_existing_branch!
256
+ return unless system "git ls-remote --exit-code --heads origin #{story_branch_name}"
257
+
258
+ print colorize(
259
+ :yellow,
260
+ "The branch for this task (#{story_branch_name}) already exists on origin, \n"\
261
+ "if you continue, it will be reset to master. \n"\
262
+ "Do you want to continue? ? (y/n) > "
263
+ )
264
+
265
+ answer = STDIN.gets.strip.downcase
266
+
267
+ exit if answer[0] != "y"
268
+ end
269
+
270
+ def create_git_branch!
271
+ system! "git checkout -b '#{story_branch_name}'"
272
+ end
273
+
274
+ def check_for_hub_credentials!
275
+ config_file = "#{ENV['HOME']}/.config/hub"
276
+ credentials_exist = File.exist?(config_file) &&
277
+ File.read(config_file).match(/oauth_token/i)
278
+
279
+ unless credentials_exist
280
+ abort <<~EOF
281
+ Your GitHub credentials are not set. Run this command:
282
+
283
+ $ hub pull-request
284
+
285
+ and when prompted for login details, enter them. It will give you
286
+ an error at the end, but you can safely ignore it.
287
+ EOF
288
+ end
289
+ end
290
+
291
+ def command?(name)
292
+ system("which #{name} 2>&1 >/dev/null")
293
+ end
294
+
295
+ def check_for_hub!
296
+ unless command?("hub")
297
+ abort <<~EOF
298
+ You need to install `hub` before you can use this program.
299
+ To fix:
300
+ $ brew install hub
301
+ EOF
302
+ end
303
+ end
304
+
305
+ def silent(*cmds)
306
+ cmds.each { |cmd| system("#{cmd} >/dev/null") }
307
+ end
308
+ end
309
+ end
310
+ end
@@ -0,0 +1,133 @@
1
+ require "fileutils"
2
+
3
+ require_relative './shared/git_config'
4
+ require_relative './shared/git_staged'
5
+ require 'bundler/setup'
6
+
7
+ module Aid
8
+ module Scripts
9
+ class Finish < Aid::Script
10
+ include Aid::GitConfig
11
+ include Aid::GitStaged
12
+
13
+ def self.description
14
+ "Commits what is currently staged with a [finishes] tag"
15
+ end
16
+
17
+ def run
18
+ within_dir(project_root) do
19
+ Bundler.with_clean_env do
20
+ check_for_staged_files!
21
+ check_for_master!
22
+
23
+ clean_up_feature_branch!
24
+ amend_commit_with_finish_message!
25
+ check_for_linter_prehook!
26
+ force_push_to_github!
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def check_for_master!
34
+ return unless `git rev-parse --abbrev-ref HEAD`.chomp == "master"
35
+
36
+ abort colorize(:red, "You are on master, can not aid finish")
37
+ end
38
+
39
+ def clean_up_feature_branch!
40
+ step "Removing any empty commits & pulling in any changes from master" do
41
+ system! "git fetch origin && git rebase -f origin/master"
42
+ end
43
+ end
44
+
45
+ def amend_commit_with_finish_message!
46
+ step "Amending your last commit with finish message" do
47
+ message = amend_message_with_tag(`git log -1 --pretty="format:%B"`.strip)
48
+ template_file = create_template_file
49
+
50
+ begin
51
+ template_file.write(message)
52
+ template_file.close
53
+
54
+ system! "git commit --amend -o -F '#{template_file.path}'"
55
+ ensure
56
+ template_file.close
57
+ template_file.unlink
58
+ end
59
+ end
60
+ end
61
+
62
+ def check_for_linter_prehook!
63
+ step "checking for linter prehook" do
64
+ pre_commit_file_location = ".git/hooks/pre-commit"
65
+ pre_commit_exists = File.exist?(pre_commit_file_location)
66
+
67
+ linter_path = ".git/hooks/rubocop-linter"
68
+ if pre_commit_exists
69
+ if File.readlines(pre_commit_file_location).grep(/rubocop[-]linter/).empty?
70
+ open(pre_commit_file_location, "a") do |f|
71
+ f << "bash #{linter_path}"
72
+ end
73
+ end
74
+ else
75
+ add_rubocop_linter linter_path
76
+ end
77
+ end
78
+ end
79
+
80
+ def add_rubocop_linter(linter_path)
81
+ dirname = File.dirname(linter_path)
82
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
83
+ linter_template = <<~RUBOCOP_LINTER
84
+ #!/bin/bash
85
+ rubocop -a
86
+ RUBOCOP_LINTER
87
+ file = File.open(linter_path, "w+")
88
+ file.puts linter_template
89
+ file.close
90
+ end
91
+
92
+ def force_push_to_github!
93
+ step "Force pushing your branch to origin" do
94
+ system! "git push --force-with-lease origin #{branch_name.inspect}"
95
+ end
96
+ end
97
+
98
+ def amend_message_with_tag(message)
99
+ finish_message = asana_finish_message
100
+ message = message.gsub("\n\n#{finish_message}", "")
101
+
102
+ "#{message}\n\n#{finish_message}"
103
+ end
104
+
105
+ def asana_finish_message
106
+ metadata = [asana_task_name, asana_task_url].compact
107
+ metadata_lines = metadata.join("\n ")
108
+
109
+ "Finishes:\n #{metadata_lines}"
110
+ end
111
+
112
+ def asana_task_name
113
+ git_config("tasks.#{asana_task_id}.name")
114
+ end
115
+
116
+ def asana_task_url
117
+ git_config("tasks.#{asana_task_id}.url")
118
+ end
119
+
120
+ def asana_task_id
121
+ @asana_task_id ||= branch_name&.split("-")&.last
122
+ end
123
+
124
+ def branch_name
125
+ `git rev-parse --abbrev-ref HEAD`.strip
126
+ end
127
+
128
+ def create_template_file
129
+ Tempfile.new("git-commit-template")
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,346 @@
1
+ require "http"
2
+ require "json"
3
+ require "slack-notifier"
4
+ require_relative "./shared/git_config"
5
+ require_relative "./shared/team"
6
+
7
+ module Aid
8
+ module Scripts
9
+ class Review < Aid::Script
10
+ include Aid::GitConfig
11
+
12
+ def self.description
13
+ "Requests a review from one or more team members"
14
+ end
15
+
16
+ def self.help
17
+ <<~HELP
18
+ Usage: $ aid review
19
+ Type aid review from your feature branch and follow the prompts
20
+ HELP
21
+ end
22
+
23
+ def run
24
+ check_for_minimum_version_of_hub!
25
+
26
+ prompt_for_reviewers!
27
+ run_aid_finish_if_needed!
28
+
29
+ change_wip_to_reviewable_label!
30
+ move_asana_task_to_pull_request_column!
31
+
32
+ step "Marking PR ready for review" do
33
+ pull_request.mark_ready_for_review!
34
+ puts "✅ done"
35
+ end
36
+
37
+ add_reviewers_to_pr!
38
+ ping_reviewers_on_slack!
39
+ end
40
+
41
+ private
42
+
43
+ def ping_reviewers_on_slack!
44
+ step "Pinging reviewers on slack" do
45
+ slack.ping(
46
+ slack_message,
47
+ username: "review-bot",
48
+ channel: slack_channel
49
+ )
50
+ end
51
+ end
52
+
53
+ def github_username
54
+ git_config("github.user")
55
+ end
56
+
57
+ def current_user_slack_tag
58
+ member = team.members.find { |member| member.github_username == github_username }
59
+ member&.slack_tag || git_config("user.name")
60
+ end
61
+
62
+
63
+ def slack_message
64
+ user_tags = @reviewers.map(&:slack_tag)
65
+
66
+ <<~MSG
67
+ :pray: *Review request from #{current_user_slack_tag}*
68
+ _#{pull_request.title}_
69
+ #{pull_request.url}
70
+
71
+ Requested: #{user_tags.join(', ')}
72
+ MSG
73
+ end
74
+
75
+ def slack
76
+ @slack ||= Slack::Notifier.new(slack_webhook)
77
+ end
78
+
79
+ def slack_webhook
80
+ "https://hooks.slack.com/services/T02A0DJMA/"\
81
+ "BN49T3ZC0/Q536v8O5pzn1NSqDJ9BpEV3s"
82
+ end
83
+
84
+ def slack_channel
85
+ "#verisure"
86
+ end
87
+
88
+ def add_reviewers_to_pr!
89
+ step "Assigning reviewers on Github" do
90
+ github_client.post(
91
+ "#{api_prefix}/pulls/#{pull_request.id}/requested_reviewers",
92
+ json: {
93
+ reviewers: @reviewers.map(&:github_username)
94
+ }
95
+ )
96
+ end
97
+ end
98
+
99
+ def check_for_minimum_version_of_hub!
100
+ match = `hub --version`.strip.match(/hub version (.+)$/)
101
+ version = Gem::Version.new(match[1])
102
+ min_version = "2.8.4"
103
+
104
+ if version < Gem::Version.new(min_version)
105
+ abort "aid review requires hub at #{min_version} or greater. "\
106
+ "Run `brew upgrade hub` to upgrade."
107
+ end
108
+ rescue StandardError
109
+ abort "Error checking for hub version. Ensure you have it installed with "\
110
+ "$ brew install hub"
111
+ end
112
+
113
+ def run_aid_finish_if_needed!
114
+ branch_commit_logs = `git log --format=full master..`.strip
115
+
116
+ if branch_commit_logs =~ %r{Finishes:.+https://app.asana.com}mi
117
+ step "Doing one last push to GitHub" do
118
+ system! "git push --force-with-lease origin #{current_branch_name}"
119
+ end
120
+ else
121
+ step "Running aid finish..." do
122
+ Finish.run
123
+ end
124
+ end
125
+ end
126
+
127
+ def move_asana_task_to_pull_request_column!
128
+ step "Moving Asana task to Pull Request column" do
129
+ asana_task.add_project(
130
+ project: asana_project.gid,
131
+ section: asana_pr_section.gid
132
+ )
133
+ end
134
+ end
135
+
136
+ def team
137
+ @team ||= Aid::Team.from_yml("#{project_root}/.team.yml")
138
+ end
139
+
140
+ def prompt_for_reviewers!
141
+ puts "Which reviewers do you want to review this PR?"
142
+ puts
143
+
144
+ @reviewers = team.prompt_for_members
145
+ puts
146
+
147
+ abort colorize(:red, "Please select a reviewer from the list") if @reviewers.empty?
148
+ end
149
+
150
+ def change_wip_to_reviewable_label!
151
+ step "Changing WIP to reviewable label" do
152
+ puts "Deleting WIP label..."
153
+
154
+ github_client.delete("#{api_prefix}/issues/#{pull_request.id}/labels/wip")
155
+
156
+ puts "Adding reviewable label..."
157
+
158
+ github_client.post(
159
+ "#{api_prefix}/issues/#{pull_request.id}/labels",
160
+ json: {
161
+ labels: ["reviewable"]
162
+ }
163
+ )
164
+ end
165
+ end
166
+
167
+ def api_prefix
168
+ "https://api.github.com/repos/#{repo_name}"
169
+ end
170
+
171
+ def github_auth_token
172
+ @github_auth_token ||= load_github_auth_token
173
+ end
174
+
175
+ def load_github_auth_token
176
+ credential_file = "#{ENV['HOME']}/.config/hub"
177
+
178
+ abort "No hub credentials in #{credential_file}" unless File.exist?(credential_file)
179
+
180
+ credentials = YAML.safe_load(File.read(credential_file))
181
+ credentials["github.com"][0]["oauth_token"]
182
+ end
183
+
184
+ def github_client
185
+ HTTP.headers("Authorization" => "token #{github_auth_token}")
186
+ end
187
+
188
+ def asana_task_id
189
+ @asana_task_id ||=
190
+ begin
191
+ result = current_branch_name.strip.match(/\d+$/)
192
+ result && result[0]
193
+ end
194
+ end
195
+
196
+ def current_branch_name
197
+ `git rev-parse --abbrev-ref HEAD`.strip
198
+ end
199
+
200
+ def repo_name
201
+ remote_url = `git remote get-url origin`.strip
202
+ result = remote_url.match(%r{github.com[:/](?<repo_name>\w+/\w+)(?:\.git)?})
203
+ result && result[:repo_name]
204
+ end
205
+
206
+ def pull_request
207
+ @pull_request ||= local_github_pull_request ||
208
+ find_remote_github_pull_request
209
+ end
210
+
211
+ def local_github_pull_request
212
+ id = git_config("asana.#{asana_task_id}.pull-request-id")
213
+ title = git_config("asana.#{asana_task_id}.name")
214
+
215
+ return nil unless id && title
216
+
217
+ PullRequest.new(
218
+ id: id,
219
+ title: title,
220
+ repo_name: repo_name
221
+ )
222
+ end
223
+
224
+ def find_remote_github_pull_request
225
+ sep = "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-="
226
+ cmd = %(hub pr list --format="#PR: %i\nTitle: %t\n\n%b\n#{sep}\n")
227
+
228
+ raw_pull_requests = `#{cmd}`.strip
229
+ pull_requests = raw_pull_requests.split(sep)
230
+ request = pull_requests.detect { |pr| pr.include?(asana_task_id) }
231
+
232
+ abort "Could not find a PR that matches story #{asana_task_id}" unless request
233
+
234
+ id = request.match(/PR: #(\d+)/)[1]
235
+ title = request.match(/Title: (.+?)\n/)[1]
236
+
237
+ PullRequest.new(
238
+ id: id,
239
+ title: title,
240
+ repo_name: repo_name
241
+ )
242
+ end
243
+
244
+ def asana_task
245
+ @asana_task ||= asana.tasks.find_by_id(asana_task_id)
246
+ end
247
+
248
+ def asana_project
249
+ @asana_project ||= asana.projects.find_by_id(asana_project_id)
250
+ end
251
+
252
+ def asana_project_id
253
+ git_config("asana.project-id").to_i
254
+ end
255
+
256
+ def asana_sections
257
+ asana.sections.find_by_project(project: asana_project.gid)
258
+ end
259
+
260
+ def asana_pr_section
261
+ asana_sections.detect do |task|
262
+ task.name =~ /Pull Request/i
263
+ end
264
+ end
265
+
266
+ def asana
267
+ @asana ||=
268
+ Asana::Client.new do |client|
269
+ client.default_headers "asana-enable" => "string_ids,new_sections"
270
+ client.authentication :access_token,
271
+ git_config("asana.personal-access-token")
272
+ end
273
+ end
274
+
275
+ class PullRequest
276
+ attr_reader :id, :title, :url
277
+
278
+ def initialize(id:, title:, repo_name:)
279
+ @id = id
280
+ @title = title
281
+ @url = "https://github.com/#{repo_name}/pull/#{id}"
282
+ end
283
+
284
+ # rubocop:disable Metrics/MethodLength
285
+ def mark_ready_for_review!
286
+ cmd = <<~CMD
287
+ hub api graphql \
288
+ -H "Accept: application/vnd.github.shadow-cat-preview+json" \
289
+ -f query='
290
+ mutation MarkPullRequestReady {
291
+ markPullRequestReadyForReview(
292
+ input: {
293
+ pullRequestId:"#{graphql_id}"
294
+ }
295
+ ) {
296
+ pullRequest {
297
+ isDraft
298
+ number
299
+ }
300
+ }
301
+ }
302
+ '
303
+ CMD
304
+
305
+ `#{cmd}`
306
+ end
307
+
308
+ private
309
+
310
+ def graphql_id
311
+ @graphql_id ||= find_graphql_id
312
+ end
313
+
314
+ def find_graphql_id
315
+ cmd = <<~CMD
316
+ hub api graphql -f query='
317
+ query FindPullRequestId {
318
+ repository(owner:"abtion", name:"verisure") {
319
+ pullRequests(states:OPEN, first: 25) {
320
+ nodes {
321
+ id
322
+ number
323
+ }
324
+ }
325
+ }
326
+ }
327
+ '
328
+ CMD
329
+
330
+ json = `#{cmd}`.strip
331
+ response = JSON.parse(json)
332
+
333
+ pull_requests = response.dig(
334
+ "data",
335
+ "repository",
336
+ "pullRequests",
337
+ "nodes"
338
+ )
339
+
340
+ request = pull_requests.find { |pr| pr["number"].to_s == id.to_s }
341
+ request["id"]
342
+ end
343
+ end
344
+ end
345
+ end
346
+ end
@@ -0,0 +1,39 @@
1
+ module Aid
2
+ module GitConfig
3
+ def git_config(key, value = nil)
4
+ if value
5
+ `git config --local --add #{key.inspect} #{value.inspect}`
6
+ else
7
+ git_value = `git config --get #{key.inspect}`.strip
8
+ git_value.empty? ? nil : git_value
9
+ end
10
+ end
11
+
12
+ def prompt_for_config!(key, prompt_msg, remedy)
13
+ value = git_config(key)
14
+
15
+ if value == "" || value.nil?
16
+ puts <<~EOF
17
+ Missing git config "#{key}":
18
+ To find this value:
19
+ #{remedy}
20
+ EOF
21
+
22
+ new_value = prompt(prompt_msg)
23
+
24
+ if new_value.empty?
25
+ abort "Empty value, aborting"
26
+ else
27
+ git_config(key, new_value)
28
+ end
29
+ end
30
+ end
31
+
32
+ def prompt(msg)
33
+ print "#{msg} > "
34
+ value = STDIN.gets.strip
35
+ puts
36
+ value
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,10 @@
1
+ module Aid
2
+ module GitStaged
3
+ def check_for_staged_files!
4
+ file_change_status_codes = "^\s*[MADRCU]"
5
+ return unless system("git status -s | grep #{file_change_status_codes.inspect} >/dev/null 2>&1")
6
+
7
+ abort colorize(:red, "You have modified/staged files, cannot aid")
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,60 @@
1
+ module Aid
2
+ class Team
3
+ attr_reader :members
4
+
5
+ def initialize(members)
6
+ @members =
7
+ members.map do |member|
8
+ Member.new(
9
+ member["name"],
10
+ member["github"],
11
+ member["slack"]
12
+ )
13
+ end
14
+ end
15
+
16
+ def self.from_yml(path)
17
+ members = YAML.safe_load(File.read(path))["team"]
18
+
19
+ Team.new(members)
20
+ end
21
+
22
+ def prompt_for_members
23
+ puts "Enter their number(s) below. For multiple team members, enter "\
24
+ "multiple numbers separated by spaces or commas."
25
+ puts
26
+
27
+ members.each.with_index do |member, index|
28
+ puts "#{index + 1}. #{member.name} (@#{member.github_username})"
29
+ end
30
+
31
+ puts
32
+ print "> "
33
+
34
+ numbers = $stdin.gets.strip.split(/[^\d]+/)
35
+
36
+ indexes =
37
+ numbers
38
+ .map { |num| num.to_i - 1 }
39
+ .reject { |num| num < 0 }
40
+
41
+ indexes.map { |index| members[index] }.compact
42
+ end
43
+
44
+ private
45
+
46
+ class Member
47
+ attr_reader :name, :github_username, :slack_user_id
48
+
49
+ def initialize(name, github_username, slack_user_id)
50
+ @name = name
51
+ @github_username = github_username
52
+ @slack_user_id = slack_user_id
53
+ end
54
+
55
+ def slack_tag
56
+ "<@#{slack_user_id}>"
57
+ end
58
+ end
59
+ end
60
+ end
data/lib/aid/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aid
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abtion-aid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abtion
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2019-11-06 00:00:00.000000000 Z
12
+ date: 2019-11-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -81,6 +81,48 @@ dependencies:
81
81
  - - ">="
82
82
  - !ruby/object:Gem::Version
83
83
  version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: http
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: slack-notifier
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: asana
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :runtime
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
84
126
  description:
85
127
  email:
86
128
  - mail@abtion.com
@@ -123,10 +165,16 @@ files:
123
165
  - lib/aid/plugins.rb
124
166
  - lib/aid/script.rb
125
167
  - lib/aid/scripts.rb
168
+ - lib/aid/scripts/begin.rb
126
169
  - lib/aid/scripts/doctor.rb
170
+ - lib/aid/scripts/finish.rb
127
171
  - lib/aid/scripts/help.rb
128
172
  - lib/aid/scripts/init.rb
129
173
  - lib/aid/scripts/new.rb
174
+ - lib/aid/scripts/review.rb
175
+ - lib/aid/scripts/shared/git_config.rb
176
+ - lib/aid/scripts/shared/git_staged.rb
177
+ - lib/aid/scripts/shared/team.rb
130
178
  - lib/aid/version.rb
131
179
  homepage: https://github.com/abtion/aid
132
180
  licenses:
@@ -147,9 +195,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
195
  - !ruby/object:Gem::Version
148
196
  version: '0'
149
197
  requirements: []
150
- rubyforge_project:
151
- rubygems_version: 2.7.7
198
+ rubygems_version: 3.0.3
152
199
  signing_key:
153
200
  specification_version: 4
154
- summary: A library to make repo scripts easy and discoverable.
201
+ summary: Abtion's development workflow tools
155
202
  test_files: []