autowow 0.3.0 → 0.4.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 +4 -4
- data/Guardfile +2 -1
- data/Rakefile +50 -3
- data/autowow.gemspec +7 -6
- data/lib/autowow.rb +1 -0
- data/lib/autowow/cli.rb +21 -13
- data/lib/autowow/commands/gem.rb +15 -0
- data/lib/autowow/commands/os.rb +11 -0
- data/lib/autowow/commands/rbenv.rb +19 -0
- data/lib/autowow/commands/vcs.rb +81 -0
- data/lib/autowow/decorators/string_decorator.rb +4 -0
- data/lib/autowow/executor.rb +49 -0
- data/lib/autowow/features/fs.rb +49 -0
- data/lib/autowow/features/gem.rb +34 -0
- data/lib/autowow/features/os.rb +16 -0
- data/lib/autowow/features/rbenv.rb +48 -0
- data/lib/autowow/features/vcs.rb +272 -0
- data/lib/autowow/log_formatter.rb +1 -1
- data/lib/autowow/version.rb +1 -1
- metadata +40 -7
- data/lib/autowow/command.rb +0 -78
- data/lib/autowow/fs.rb +0 -41
- data/lib/autowow/gem.rb +0 -32
- data/lib/autowow/ruby.rb +0 -38
- data/lib/autowow/vcs.rb +0 -304
data/lib/autowow/command.rb
DELETED
@@ -1,78 +0,0 @@
|
|
1
|
-
require 'open3'
|
2
|
-
|
3
|
-
module Autowow
|
4
|
-
class Command
|
5
|
-
|
6
|
-
include EasyLogging
|
7
|
-
|
8
|
-
def self.run(*args)
|
9
|
-
Command.new(*args).check.explain.chronic_execute
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.run_dry(*args)
|
13
|
-
Command.new(*args).silent_check.execute
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.popen3_reader(*args)
|
17
|
-
args.each do |arg|
|
18
|
-
reader = <<-EOF
|
19
|
-
def #{arg}
|
20
|
-
@#{arg} = @#{arg}.read.rstrip unless @#{arg}.is_a?(String)
|
21
|
-
return @#{arg}
|
22
|
-
end
|
23
|
-
EOF
|
24
|
-
class_eval(reader)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
popen3_reader :stdin, :stdout, :stderr
|
29
|
-
attr_reader :wait_thr
|
30
|
-
|
31
|
-
def initialize(*args)
|
32
|
-
@cmd = args
|
33
|
-
end
|
34
|
-
|
35
|
-
def explain
|
36
|
-
logger.debug(@cmd.join(' ')) unless @cmd.empty?
|
37
|
-
self
|
38
|
-
end
|
39
|
-
|
40
|
-
def execute
|
41
|
-
@stdin, @stdout, @stderr, @wait_thr = Open3.popen3(*@cmd) unless @cmd.empty?
|
42
|
-
self
|
43
|
-
end
|
44
|
-
|
45
|
-
def chronic_execute
|
46
|
-
@stdin, @stdout, @stderr, @wait_thr = Open3.popen3(*@cmd) unless @cmd.empty?
|
47
|
-
logger.error(stderr) unless stderr.empty?
|
48
|
-
self
|
49
|
-
end
|
50
|
-
|
51
|
-
def silent_check
|
52
|
-
@stdin, @stdout, @stderr, @wait_thr = Open3.popen3('which', @cmd[0])
|
53
|
-
unless output_matches?(not_empty_matcher)
|
54
|
-
yield if block_given?
|
55
|
-
@cmd = []
|
56
|
-
end
|
57
|
-
self
|
58
|
-
end
|
59
|
-
|
60
|
-
def check
|
61
|
-
silent_check do
|
62
|
-
logger.info("Skipping '#{@cmd.join(' ')}' because command '#{@cmd[0]}' is not found.")
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def output_matches?(matcher)
|
67
|
-
stdout.match(matcher)
|
68
|
-
end
|
69
|
-
|
70
|
-
def output_does_not_match?(matcher)
|
71
|
-
!output_matches?(matcher)
|
72
|
-
end
|
73
|
-
|
74
|
-
def not_empty_matcher
|
75
|
-
%r{.*\S.*}
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
data/lib/autowow/fs.rb
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
require_relative 'time_difference'
|
2
|
-
|
3
|
-
module Autowow
|
4
|
-
class Fs
|
5
|
-
using RefinedTimeDifference
|
6
|
-
|
7
|
-
def self.ls_dirs
|
8
|
-
Dir.glob(File.expand_path('./*/')).select {|f| File.directory? f}
|
9
|
-
end
|
10
|
-
|
11
|
-
def self.latest(files)
|
12
|
-
files.sort_by{ |f| File.mtime(f) }.reverse!.first
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.older_than(files, quantity, unit)
|
16
|
-
files.select do |dir|
|
17
|
-
TimeDifference.between(File.mtime(dir), Time.now).public_send("in_#{unit}") > quantity
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.for_dirs(dirs)
|
22
|
-
dirs.each do |working_dir|
|
23
|
-
# TODO: add handling of directories via extra param to popen3
|
24
|
-
# https://stackoverflow.com/a/10148084/2771889
|
25
|
-
Dir.chdir(working_dir) do
|
26
|
-
yield working_dir
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def self.in_place_or_subdirs(in_place)
|
32
|
-
if in_place
|
33
|
-
yield
|
34
|
-
else
|
35
|
-
for_dirs(ls_dirs) do
|
36
|
-
yield
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
data/lib/autowow/gem.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
require_relative 'vcs'
|
2
|
-
require_relative 'command'
|
3
|
-
|
4
|
-
module Autowow
|
5
|
-
class Gem
|
6
|
-
include EasyLogging
|
7
|
-
|
8
|
-
def self.gem_release
|
9
|
-
start_status = Vcs.status
|
10
|
-
logger.info(start_status)
|
11
|
-
working_branch = Vcs.current_branch
|
12
|
-
logger.error("Not on master.") and return unless working_branch.eql?('master')
|
13
|
-
Vcs.push
|
14
|
-
|
15
|
-
Vcs.on_branch('release') do
|
16
|
-
Vcs.pull
|
17
|
-
Vcs.rebase(working_branch)
|
18
|
-
release
|
19
|
-
end
|
20
|
-
|
21
|
-
logger.info(Vcs.status)
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.release
|
25
|
-
Command.run('rake', 'release')
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.clean
|
29
|
-
Command.run('gem', 'clean')
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
data/lib/autowow/ruby.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
module Autowow
|
2
|
-
class Ruby
|
3
|
-
include EasyLogging
|
4
|
-
|
5
|
-
def self.used_versions
|
6
|
-
rubies = []
|
7
|
-
Fs.in_place_or_subdirs(Vcs.is_git?(Vcs.status_dry)) do
|
8
|
-
rubies.push(version)
|
9
|
-
end
|
10
|
-
rubies.uniq
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.version
|
14
|
-
Command.run_dry('rbenv', 'local').stdout
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.installed_versions
|
18
|
-
Command.run_dry('rbenv', 'versions', '--bare', '--skip-aliases').stdout.each_line.map(&:strip)
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.aliases
|
22
|
-
aliases = {}
|
23
|
-
Command.run_dry('rbenv', 'alias').stdout.each_line do |line|
|
24
|
-
aliases[line.strip.split(' => ')[0]] = line.strip.split(' => ')[1]
|
25
|
-
end
|
26
|
-
aliases
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.obsolete_versions
|
30
|
-
alias_map = aliases
|
31
|
-
used_versions_and_aliases = used_versions
|
32
|
-
used_versions.each do |v|
|
33
|
-
used_versions_and_aliases.push(alias_map[v]) if alias_map.has_key?(v)
|
34
|
-
end
|
35
|
-
installed_versions - used_versions_and_aliases
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
data/lib/autowow/vcs.rb
DELETED
@@ -1,304 +0,0 @@
|
|
1
|
-
require 'uri'
|
2
|
-
require 'net/https'
|
3
|
-
require 'net/http'
|
4
|
-
require 'json'
|
5
|
-
require 'launchy'
|
6
|
-
|
7
|
-
require_relative 'command'
|
8
|
-
require_relative 'decorators/string_decorator'
|
9
|
-
require_relative 'fs'
|
10
|
-
require_relative 'time_difference'
|
11
|
-
require_relative 'gem'
|
12
|
-
require_relative 'ruby'
|
13
|
-
|
14
|
-
module Autowow
|
15
|
-
class Vcs
|
16
|
-
include EasyLogging
|
17
|
-
include StringDecorator
|
18
|
-
|
19
|
-
using RefinedTimeDifference
|
20
|
-
|
21
|
-
def self.branch_merged
|
22
|
-
start_status = status
|
23
|
-
logger.info(start_status)
|
24
|
-
working_branch = current_branch
|
25
|
-
logger.error("Nothing to do.") and return if working_branch.eql?('master')
|
26
|
-
|
27
|
-
keep_changes do
|
28
|
-
checkout('master')
|
29
|
-
pull
|
30
|
-
end
|
31
|
-
branch_force_delete(working_branch)
|
32
|
-
|
33
|
-
logger.info(status)
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.update_projects
|
37
|
-
Fs.in_place_or_subdirs(is_git?(status_dry)) do
|
38
|
-
update_project
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.update_project
|
43
|
-
start_status = status_dry
|
44
|
-
return unless is_git?(start_status)
|
45
|
-
logger.info("Updating #{File.expand_path('.')} ...")
|
46
|
-
logger.warn("Skipped: uncommitted changes on master.") and return if uncommitted_changes?(start_status) and current_branch.eql?('master')
|
47
|
-
|
48
|
-
on_branch('master') do
|
49
|
-
has_upstream?(remotes.stdout) ? pull_upstream : pull
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.clear_branches
|
54
|
-
logger.info(branch.stdout)
|
55
|
-
working_branch = current_branch
|
56
|
-
master_branch = 'master'
|
57
|
-
|
58
|
-
(branches - [master_branch, working_branch]).each do |branch|
|
59
|
-
branch_force_delete(branch) if branch_pushed(branch)
|
60
|
-
end
|
61
|
-
|
62
|
-
logger.info(branch.stdout)
|
63
|
-
end
|
64
|
-
|
65
|
-
def self.add_upstream
|
66
|
-
start_status = status_dry
|
67
|
-
logger.error("Not a git repository.") and return unless is_git?(start_status)
|
68
|
-
remote_list = remotes.stdout
|
69
|
-
logger.warn("Already has upstream.") and return if has_upstream?(remote_list)
|
70
|
-
logger.info(remote_list)
|
71
|
-
|
72
|
-
url = URI.parse(origin_push_url(remote_list))
|
73
|
-
host = "api.#{url.host}"
|
74
|
-
path = "/repos#{url.path}"
|
75
|
-
request = Net::HTTP.new(host, url.port)
|
76
|
-
request.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
77
|
-
request.use_ssl = url.scheme == 'https'
|
78
|
-
response = request.get(path)
|
79
|
-
|
80
|
-
if response.kind_of?(Net::HTTPRedirection)
|
81
|
-
logger.error('Repository moved / renamed. Update remote or implement redirect handling. :)')
|
82
|
-
elsif response.kind_of?(Net::HTTPNotFound)
|
83
|
-
logger.error('Repository not found. Maybe it is private.')
|
84
|
-
elsif response.kind_of?(Net::HTTPSuccess)
|
85
|
-
parsed_response = JSON.parse(response.body)
|
86
|
-
logger.warn('Not a fork.') and return unless parsed_response['fork']
|
87
|
-
parent_url = parsed_response.dig('parent', 'html_url')
|
88
|
-
add_remote('upstream', parent_url) unless parent_url.to_s.empty?
|
89
|
-
logger.info(remotes.stdout)
|
90
|
-
else
|
91
|
-
logger.error("Github API (#{url.scheme}://#{host}#{path}) could not be reached: #{response.body}")
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def self.greet(latest_project_info = nil)
|
96
|
-
logger.info("\nGood morning!\n\n")
|
97
|
-
if is_git?(status_dry)
|
98
|
-
logger.error('Inside repo, cannot show report about all repos.')
|
99
|
-
else
|
100
|
-
latest_project_info ||= get_latest_project_info
|
101
|
-
logger.info(latest_project_info)
|
102
|
-
check_projects_older_than(1, :months)
|
103
|
-
end
|
104
|
-
logger.info("\nThe following Ruby versions are not used by any projects, maybe consider removing them?\n #{Ruby.obsolete_versions.join("\n ")}")
|
105
|
-
end
|
106
|
-
|
107
|
-
def self.hi
|
108
|
-
logger.error("In a git repository. Try 1 level higher.") && return if is_git?(status_dry)
|
109
|
-
latest_project_info = get_latest_project_info
|
110
|
-
logger.info("\nHang on, updating your local projects and remote forks...\n\n")
|
111
|
-
git_projects.each do |project|
|
112
|
-
Dir.chdir(project) do
|
113
|
-
logger.info("\nGetting #{project} in shape...")
|
114
|
-
yield if block_given?
|
115
|
-
update_project
|
116
|
-
end
|
117
|
-
end
|
118
|
-
greet(latest_project_info)
|
119
|
-
end
|
120
|
-
|
121
|
-
def self.hi!
|
122
|
-
logger.error("In a git repository. Try 1 level higher.") && return if is_git?(status_dry)
|
123
|
-
hi do
|
124
|
-
logger.info('Removing unused branches...')
|
125
|
-
clear_branches
|
126
|
-
logger.info('Adding upstream...')
|
127
|
-
add_upstream
|
128
|
-
logger.info('Removing unused gems...')
|
129
|
-
logger.info(Gem.clean.stdout)
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
def self.open
|
134
|
-
Launchy.open(origin_push_url(remotes.stdout))
|
135
|
-
end
|
136
|
-
|
137
|
-
def self.get_latest_project_info
|
138
|
-
latest = latest_repo
|
139
|
-
time_diff = TimeDifference.between(File.mtime(latest), Time.now).humanize_higher_than(:days).downcase
|
140
|
-
time_diff_text = time_diff.empty? ? 'recently' : "#{time_diff} ago"
|
141
|
-
"It looks like you were working on #{File.basename(latest)} #{time_diff_text}.\n\n"
|
142
|
-
end
|
143
|
-
|
144
|
-
def self.latest_repo
|
145
|
-
Fs.latest(git_projects)
|
146
|
-
end
|
147
|
-
|
148
|
-
def self.check_projects_older_than(quantity, unit)
|
149
|
-
old_projects = Fs.older_than(git_projects, quantity, unit)
|
150
|
-
deprecated_projects = old_projects.reject do |project|
|
151
|
-
Dir.chdir(project) { branches.reject{ |branch| branch_pushed(branch) }.any? }
|
152
|
-
end
|
153
|
-
|
154
|
-
logger.info("The following projects have not been touched for more than #{quantity} #{unit} and all changes have been pushed, maybe consider removing them?") unless deprecated_projects.empty?
|
155
|
-
deprecated_projects.each do |project|
|
156
|
-
time_diff = TimeDifference.between(File.mtime(project), Time.now).humanize_higher_than(:weeks).downcase
|
157
|
-
logger.info(" #{File.basename(project)} (#{time_diff})")
|
158
|
-
end
|
159
|
-
end
|
160
|
-
|
161
|
-
def self.stash
|
162
|
-
Command.run('git', 'stash').output_does_not_match?(%r{No local changes to save})
|
163
|
-
end
|
164
|
-
|
165
|
-
def self.current_branch
|
166
|
-
Command.run_dry('git', 'symbolic-ref', '--short', 'HEAD').stdout
|
167
|
-
end
|
168
|
-
|
169
|
-
def self.status
|
170
|
-
status = Command.run('git', 'status')
|
171
|
-
status.stdout + status.stderr
|
172
|
-
end
|
173
|
-
|
174
|
-
def self.status_dry
|
175
|
-
status = Command.run_dry('git', 'status')
|
176
|
-
status.stdout + status.stderr
|
177
|
-
end
|
178
|
-
|
179
|
-
def self.checkout(existing_branch)
|
180
|
-
Command.run('git', 'checkout', existing_branch)
|
181
|
-
end
|
182
|
-
|
183
|
-
def self.create(branch)
|
184
|
-
Command.run('git', 'checkout', '-b', branch)
|
185
|
-
Command.run('git', 'push', '--set-upstream', 'origin', branch)
|
186
|
-
end
|
187
|
-
|
188
|
-
def self.pull
|
189
|
-
Command.run('git', 'pull')
|
190
|
-
end
|
191
|
-
|
192
|
-
def self.pull_upstream
|
193
|
-
Command.run('git', 'fetch', 'upstream')
|
194
|
-
Command.run('git', 'merge', 'upstream/master')
|
195
|
-
Command.run('git', 'push', 'origin', 'master')
|
196
|
-
end
|
197
|
-
|
198
|
-
def self.branch
|
199
|
-
Command.run('git', 'branch')
|
200
|
-
end
|
201
|
-
|
202
|
-
def self.stash_pop
|
203
|
-
Command.run('git', 'stash', 'pop')
|
204
|
-
end
|
205
|
-
|
206
|
-
def self.branch_force_delete(branch)
|
207
|
-
Command.run('git', 'branch', '-D', branch)
|
208
|
-
end
|
209
|
-
|
210
|
-
def self.remotes
|
211
|
-
Command.run_dry('git', 'remote', '-v')
|
212
|
-
end
|
213
|
-
|
214
|
-
def self.has_upstream?(remotes)
|
215
|
-
remotes.include?('upstream')
|
216
|
-
end
|
217
|
-
|
218
|
-
def self.uncommitted_changes?(start_status)
|
219
|
-
!(start_status.include?('nothing to commit, working tree clean') or start_status.include?('nothing added to commit but untracked files present'))
|
220
|
-
end
|
221
|
-
|
222
|
-
def self.is_git?(start_status)
|
223
|
-
!start_status.include?('Not a git repository')
|
224
|
-
end
|
225
|
-
|
226
|
-
def self.origin_push_url(remotes)
|
227
|
-
# Order is important: first try to match "url" in "#{url}.git" as non-dot_git matchers would include ".git" in the match
|
228
|
-
origin_push_url_ssl_dot_git(remotes) or
|
229
|
-
origin_push_url_ssl(remotes)or
|
230
|
-
origin_push_url_https_dot_git(remotes) or
|
231
|
-
origin_push_url_https(remotes)
|
232
|
-
end
|
233
|
-
|
234
|
-
def self.origin_push_url_https(remotes)
|
235
|
-
remotes[%r{(?<=origin(\s))http(s?)://[a-zA-Z\-_./]*(?=(\s)\(push\))}]
|
236
|
-
end
|
237
|
-
|
238
|
-
def self.origin_push_url_https_dot_git(remotes)
|
239
|
-
remotes[%r{(?<=origin(\s))http(s?)://[a-zA-Z\-_./]*(?=(\.)git(\s)\(push\))}]
|
240
|
-
end
|
241
|
-
|
242
|
-
def self.origin_push_url_ssl_dot_git(remotes)
|
243
|
-
url = remotes[%r{(?<=origin(\s)git@)[a-zA-Z\-_./:]*(?=(\.)git(\s)\(push\))}]
|
244
|
-
"https://#{url.gsub(':', '/')}" if url
|
245
|
-
end
|
246
|
-
|
247
|
-
def self.origin_push_url_ssl(remotes)
|
248
|
-
url = remotes[%r{(?<=origin(\s)git@)[a-zA-Z\-_./:]*(?=(\s)\(push\))}]
|
249
|
-
"https://#{url.gsub(':', '/')}" if url
|
250
|
-
end
|
251
|
-
|
252
|
-
def self.add_remote(name, url)
|
253
|
-
Command.run('git', 'remote', 'add', name, url)
|
254
|
-
end
|
255
|
-
|
256
|
-
def self.on_branch(branch)
|
257
|
-
keep_changes do
|
258
|
-
working_branch = current_branch
|
259
|
-
switch_needed = !working_branch.eql?(branch)
|
260
|
-
if switch_needed
|
261
|
-
result = checkout(branch)
|
262
|
-
create(branch) if result.stderr.eql?("error: pathspec '#{branch}' did not match any file(s) known to git.")
|
263
|
-
end
|
264
|
-
|
265
|
-
yield
|
266
|
-
|
267
|
-
checkout(working_branch) if switch_needed
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
def self.keep_changes
|
272
|
-
status = status_dry
|
273
|
-
pop_stash = uncommitted_changes?(status)
|
274
|
-
stash if pop_stash
|
275
|
-
yield
|
276
|
-
stash_pop if pop_stash
|
277
|
-
end
|
278
|
-
|
279
|
-
def self.git_projects
|
280
|
-
Fs.ls_dirs.select do |dir|
|
281
|
-
Dir.chdir(dir) do
|
282
|
-
is_git?(status_dry)
|
283
|
-
end
|
284
|
-
end
|
285
|
-
end
|
286
|
-
|
287
|
-
def self.branch_pushed(branch)
|
288
|
-
Command.run_dry('git', 'log', branch, '--not', '--remotes').stdout.empty?
|
289
|
-
end
|
290
|
-
|
291
|
-
def self.branches
|
292
|
-
branches = Command.run_dry('git', 'for-each-ref', "--format='%(refname)'", 'refs/heads/').stdout
|
293
|
-
branches.each_line.map { |line| line.strip[%r{(?<='refs/heads/)(.*)(?=')}] }
|
294
|
-
end
|
295
|
-
|
296
|
-
def self.push
|
297
|
-
Command.run('git', 'push')
|
298
|
-
end
|
299
|
-
|
300
|
-
def self.rebase(branch)
|
301
|
-
Command.run('git', 'rebase', branch)
|
302
|
-
end
|
303
|
-
end
|
304
|
-
end
|