wagemage 1.0.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 +7 -0
- data/.github/workflows/tests.yml +18 -0
- data/.gitignore +9 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +74 -0
- data/LICENSE.txt +21 -0
- data/README.md +242 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/rake +29 -0
- data/bin/setup +6 -0
- data/examples/add_test_file +35 -0
- data/examples/consolidate_pushes_during_release +63 -0
- data/examples/enable_teaspoon_tests +83 -0
- data/examples/find_decorated_test +35 -0
- data/examples/find_teaspoon_tests +32 -0
- data/examples/ignore_remote_rubocop_config +48 -0
- data/examples/nextyear +43 -0
- data/exe/wagemage +9 -0
- data/lib/wagemage.rb +31 -0
- data/lib/wagemage/cli.rb +191 -0
- data/lib/wagemage/helpers.rb +30 -0
- data/lib/wagemage/repo.rb +78 -0
- data/lib/wagemage/version.rb +3 -0
- data/wagemage.gemspec +36 -0
- metadata +157 -0
data/lib/wagemage/cli.rb
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
module Wagemage
|
2
|
+
class CLI
|
3
|
+
include Wagemage::Helpers
|
4
|
+
|
5
|
+
def initialize(args)
|
6
|
+
@options = Slop.parse(args) do |option|
|
7
|
+
option.bool '-h', '--help', 'print this help'
|
8
|
+
option.on '-v', '--version', 'print the version'
|
9
|
+
|
10
|
+
option.string '-o', '--org', 'github org'
|
11
|
+
option.string '-r', '--repo', 'regex against which to match repo names'
|
12
|
+
option.string '-b', '--branch', 'regex against which to match branches'
|
13
|
+
|
14
|
+
option.path '-s', '--script', "the script to run on each repo's branch"
|
15
|
+
|
16
|
+
option.bool '--first-branch', 'operate only on the "oldest" branch'
|
17
|
+
option.array '--reviewers', 'array of github users to put on the PR'
|
18
|
+
option.string(
|
19
|
+
'--branch-prefix',
|
20
|
+
'prefix of the new branch',
|
21
|
+
default: 'wagemage'
|
22
|
+
)
|
23
|
+
|
24
|
+
option.bool '--debug', "don't push or issue PR, keep the tmp directory"
|
25
|
+
end
|
26
|
+
|
27
|
+
validate_options!
|
28
|
+
|
29
|
+
token = ENV['WAGEMAGE_GITHUB_TOKEN'] || request_token
|
30
|
+
@okclient = Octokit::Client.new(access_token: token)
|
31
|
+
|
32
|
+
@tmpdir = Dir.mktmpdir
|
33
|
+
@script_path = @options[:script].expand_path.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def run
|
37
|
+
raise Error, 'No repos found' if repos.empty?
|
38
|
+
|
39
|
+
say "#{repos.size} repo(s) found:", space: true
|
40
|
+
repos.each { |r| say "* #{r.name}", color: :green }
|
41
|
+
|
42
|
+
say 'Would you like to clone these repos? (Y/n)', space: true
|
43
|
+
abort if ask.casecmp?('n')
|
44
|
+
|
45
|
+
begin
|
46
|
+
clone_repos
|
47
|
+
display_branch_list
|
48
|
+
|
49
|
+
say <<~MESSAGE, space: true, color: :yellow
|
50
|
+
You're about to run this script against the aforementioned list:
|
51
|
+
=> #{@script_path}
|
52
|
+
MESSAGE
|
53
|
+
|
54
|
+
if @options[:debug]
|
55
|
+
say "(--debug flag enabled. No code will be pushed.)", color: :green
|
56
|
+
else
|
57
|
+
say "(--debug flag NOT enabled. Code may be pushed.)", color: :red
|
58
|
+
end
|
59
|
+
|
60
|
+
say "Would you like to execute this script? (Y/n)"
|
61
|
+
abort if ask.casecmp?('n')
|
62
|
+
|
63
|
+
repos.each do |repo|
|
64
|
+
repo.branches.each_with_index do |branch, index|
|
65
|
+
if index > 0
|
66
|
+
say "=> Skipping #{repo.name}:#{branch}", color: :yellow
|
67
|
+
next
|
68
|
+
end
|
69
|
+
|
70
|
+
new_branch = [
|
71
|
+
@options[:branch_prefix],
|
72
|
+
branch,
|
73
|
+
Time.now.to_i
|
74
|
+
].join('/')
|
75
|
+
|
76
|
+
repo.checkout! branch
|
77
|
+
repo.checkout! new_branch, create: true
|
78
|
+
|
79
|
+
say "=> Running script on #{repo.name}:#{new_branch}"
|
80
|
+
script_result = command(
|
81
|
+
[@script_path, repo.clone_dir, repo.name, branch].join(' ')
|
82
|
+
)
|
83
|
+
|
84
|
+
unless script_result[:stderr].empty?
|
85
|
+
say script_result[:stderr], color: :red
|
86
|
+
end
|
87
|
+
|
88
|
+
if script_result[:status].success? && !repo.has_changed?
|
89
|
+
say 'SCRIPT SUCCEEDED BUT NO CHANGES TO COMMIT!', color: :yellow
|
90
|
+
next
|
91
|
+
elsif !script_result[:status].success?
|
92
|
+
say 'SCRIPT FAILED!', color: :red
|
93
|
+
next
|
94
|
+
end
|
95
|
+
|
96
|
+
say 'SCRIPT SUCCEEDED! COMMITTING CHANGES!', color: :green
|
97
|
+
|
98
|
+
repo.add_all!
|
99
|
+
repo.commit! script_result[:stdout]
|
100
|
+
|
101
|
+
if @options[:debug]
|
102
|
+
say 'DEBUG ENABLED! SKIPPING PUSH & PULL REQUEST!', color: :yellow
|
103
|
+
next
|
104
|
+
end
|
105
|
+
|
106
|
+
repo.push!
|
107
|
+
|
108
|
+
pr_result = repo.pull_request!(branch, @options[:reviewers])
|
109
|
+
|
110
|
+
if pr_result[:status].success?
|
111
|
+
say "=> PULL REQUEST URL: #{pr_result[:stdout]}", color: :green
|
112
|
+
else
|
113
|
+
say "=> PULL REQUEST FAILED!", color: :red
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
ensure
|
118
|
+
if @options[:debug]
|
119
|
+
say <<~MESSAGE, space: :true, color: :yellow
|
120
|
+
The temporary directory has been retained because you have specified
|
121
|
+
the --debug flag. You can view it here:
|
122
|
+
=> #{@tmpdir}
|
123
|
+
MESSAGE
|
124
|
+
else
|
125
|
+
FileUtils.remove_entry(@tmpdir)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def repos
|
133
|
+
@repos ||= begin
|
134
|
+
repos = @options[:org].nil? ?
|
135
|
+
@okclient.repos :
|
136
|
+
@okclient.org_repos(@options[:org])
|
137
|
+
|
138
|
+
last_response = @okclient.last_response
|
139
|
+
while last_response.rels[:next] do
|
140
|
+
repos.concat(last_response.rels[:next].get.data)
|
141
|
+
last_response = last_response.rels[:next].get
|
142
|
+
end
|
143
|
+
|
144
|
+
repos.map! { |r| Wagemage::Repo.new(r, @tmpdir, @options[:branch]) }
|
145
|
+
|
146
|
+
return repos if @options[:repo].nil?
|
147
|
+
|
148
|
+
repo_name_regex = Regexp.new(@options[:repo])
|
149
|
+
repos.select { |r| r.name =~ repo_name_regex }
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def clone_repos
|
154
|
+
repos.each do |repo|
|
155
|
+
say "=> Cloning #{repo.name} to #{repo.clone_dir}", color: :light_blue
|
156
|
+
repo.clone!
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def display_branch_list
|
161
|
+
repos.each do |repo|
|
162
|
+
say "* #{repo.name}:", space: true
|
163
|
+
|
164
|
+
if repo.branches.empty?
|
165
|
+
say ' - NONE FOUND', color: :red
|
166
|
+
next
|
167
|
+
end
|
168
|
+
|
169
|
+
if @options[:first_branch]
|
170
|
+
say " - #{repo.branches.first}", color: :green
|
171
|
+
repo.branches[1..-1].each { |b| say " - #{b}" }
|
172
|
+
else
|
173
|
+
repo.branches.each { |b| say " - #{b}", color: :green }
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def request_token
|
179
|
+
say 'Github Personal Access Token missing', color: :red
|
180
|
+
say 'Please supply it now:'
|
181
|
+
ask
|
182
|
+
end
|
183
|
+
|
184
|
+
def validate_options!
|
185
|
+
abort(@options.to_s) if @options.help?
|
186
|
+
abort("Wagemage v#{Wagemage::VERSION}") if @options.version?
|
187
|
+
|
188
|
+
raise OptionError if @options[:script].nil?
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Wagemage
|
2
|
+
module Helpers
|
3
|
+
def say(message, space: false, color: :white)
|
4
|
+
puts if space
|
5
|
+
puts message.colorize(color.to_sym)
|
6
|
+
end
|
7
|
+
|
8
|
+
def ask
|
9
|
+
STDIN.gets.chomp
|
10
|
+
end
|
11
|
+
|
12
|
+
def warning(message)
|
13
|
+
say(message, color: :red)
|
14
|
+
end
|
15
|
+
|
16
|
+
def command(cmd, chdir: Dir.pwd, error: false)
|
17
|
+
stdout, stderr, status = Open3.capture3(cmd, chdir: chdir)
|
18
|
+
|
19
|
+
unless status.success?
|
20
|
+
error ? (raise Error, stderr) : warning(stderr)
|
21
|
+
end
|
22
|
+
|
23
|
+
{
|
24
|
+
stdout: stdout,
|
25
|
+
stderr: stderr,
|
26
|
+
status: status
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Wagemage
|
2
|
+
class Repo
|
3
|
+
attr_reader :clone_dir
|
4
|
+
|
5
|
+
def initialize(info, dir, branch_pattern)
|
6
|
+
@info = info
|
7
|
+
@clone_dir = [dir, info[:full_name]].join('/')
|
8
|
+
@branch_pattern = branch_pattern
|
9
|
+
end
|
10
|
+
|
11
|
+
def name
|
12
|
+
@info[:full_name]
|
13
|
+
end
|
14
|
+
|
15
|
+
def url
|
16
|
+
@info[:ssh_url]
|
17
|
+
end
|
18
|
+
|
19
|
+
def clone!
|
20
|
+
Wagemage.command("git clone #{url} #{clone_dir}", error: true)
|
21
|
+
end
|
22
|
+
|
23
|
+
def branches
|
24
|
+
@branches ||= begin
|
25
|
+
result = Wagemage.command('git branch -a', chdir: @clone_dir)
|
26
|
+
|
27
|
+
return [] unless result[:status].success?
|
28
|
+
|
29
|
+
branch_list =
|
30
|
+
result[:stdout]
|
31
|
+
.split("\n")
|
32
|
+
.select { |b| b.include?('remotes/origin/') }
|
33
|
+
.reject { |b| b.include?('->') }
|
34
|
+
.map { |b| b.split('/')[2..-1].join('/') }
|
35
|
+
|
36
|
+
if branch_list.include?('master')
|
37
|
+
branch_list
|
38
|
+
.reject! { |b| b == 'master' }
|
39
|
+
.push('master')
|
40
|
+
end
|
41
|
+
|
42
|
+
return branch_list if @branch_pattern.nil?
|
43
|
+
|
44
|
+
branch_name_regex = Regexp.new(@branch_pattern)
|
45
|
+
branch_list.select { |b| b =~ branch_name_regex }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def checkout!(ref, create: false)
|
50
|
+
cmd = create ? "git checkout -b #{ref}" : "git checkout #{ref}"
|
51
|
+
Wagemage.command(cmd, chdir: @clone_dir)
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_all!
|
55
|
+
Wagemage.command('git add .', chdir: @clone_dir)
|
56
|
+
end
|
57
|
+
|
58
|
+
def commit!(message)
|
59
|
+
Wagemage.command(%Q[git commit -m "#{message}"], chdir: @clone_dir)
|
60
|
+
end
|
61
|
+
|
62
|
+
def push!
|
63
|
+
Wagemage.command('git push origin HEAD', chdir: @clone_dir)
|
64
|
+
end
|
65
|
+
|
66
|
+
def pull_request!(base_branch, reviewers = [])
|
67
|
+
cmd = "hub pull-request --no-edit -b #{base_branch}"
|
68
|
+
cmd = [cmd, '-r', reviewers.join(',')].join(' ') unless reviewers.empty?
|
69
|
+
|
70
|
+
Wagemage.command(cmd, chdir: @clone_dir)
|
71
|
+
end
|
72
|
+
|
73
|
+
def has_changed?
|
74
|
+
result = Wagemage.command('git status -s', chdir: @clone_dir)
|
75
|
+
!result[:stdout].empty?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/wagemage.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "wagemage/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "wagemage"
|
7
|
+
spec.version = Wagemage::VERSION
|
8
|
+
spec.authors = ["Curt Howard"]
|
9
|
+
spec.email = ["curt@portugly.com"]
|
10
|
+
|
11
|
+
spec.summary = "A CLI for making changes to many Github repos"
|
12
|
+
spec.description = "A CLI for making changes to many Github repos"
|
13
|
+
spec.homepage = "https://github.com/meowsus/wagemage"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata["source_code_uri"] = "https://github.com/meowsus/wagemage"
|
18
|
+
|
19
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
20
|
+
`git ls-files -z`
|
21
|
+
.split("\x0")
|
22
|
+
.reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.bindir = "exe"
|
26
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
30
|
+
spec.add_development_dependency "rake", "~> 12.3.3"
|
31
|
+
spec.add_development_dependency "minitest", "~> 5.0"
|
32
|
+
|
33
|
+
spec.add_dependency "slop", "~> 4.7"
|
34
|
+
spec.add_dependency "colorize", "~> 0.8"
|
35
|
+
spec.add_dependency "octokit", "~> 4.14"
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wagemage
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Curt Howard
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-11-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 12.3.3
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 12.3.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: slop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.7'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.7'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: colorize
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.8'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.8'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: octokit
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '4.14'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '4.14'
|
97
|
+
description: A CLI for making changes to many Github repos
|
98
|
+
email:
|
99
|
+
- curt@portugly.com
|
100
|
+
executables:
|
101
|
+
- wagemage
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- ".github/workflows/tests.yml"
|
106
|
+
- ".gitignore"
|
107
|
+
- ".rubocop.yml"
|
108
|
+
- ".travis.yml"
|
109
|
+
- CODE_OF_CONDUCT.md
|
110
|
+
- Gemfile
|
111
|
+
- Gemfile.lock
|
112
|
+
- LICENSE.txt
|
113
|
+
- README.md
|
114
|
+
- Rakefile
|
115
|
+
- bin/console
|
116
|
+
- bin/rake
|
117
|
+
- bin/setup
|
118
|
+
- examples/add_test_file
|
119
|
+
- examples/consolidate_pushes_during_release
|
120
|
+
- examples/enable_teaspoon_tests
|
121
|
+
- examples/find_decorated_test
|
122
|
+
- examples/find_teaspoon_tests
|
123
|
+
- examples/ignore_remote_rubocop_config
|
124
|
+
- examples/nextyear
|
125
|
+
- exe/wagemage
|
126
|
+
- lib/wagemage.rb
|
127
|
+
- lib/wagemage/cli.rb
|
128
|
+
- lib/wagemage/helpers.rb
|
129
|
+
- lib/wagemage/repo.rb
|
130
|
+
- lib/wagemage/version.rb
|
131
|
+
- wagemage.gemspec
|
132
|
+
homepage: https://github.com/meowsus/wagemage
|
133
|
+
licenses:
|
134
|
+
- MIT
|
135
|
+
metadata:
|
136
|
+
homepage_uri: https://github.com/meowsus/wagemage
|
137
|
+
source_code_uri: https://github.com/meowsus/wagemage
|
138
|
+
post_install_message:
|
139
|
+
rdoc_options: []
|
140
|
+
require_paths:
|
141
|
+
- lib
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
requirements: []
|
153
|
+
rubygems_version: 3.1.2
|
154
|
+
signing_key:
|
155
|
+
specification_version: 4
|
156
|
+
summary: A CLI for making changes to many Github repos
|
157
|
+
test_files: []
|