autowow 0.12.1 → 0.13.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 +5 -5
- data/.gitignore +14 -14
- data/.rspec +2 -2
- data/.rubocop.yml +118 -118
- data/.travis.yml +11 -11
- data/Gemfile +6 -6
- data/Guardfile +22 -22
- data/LICENSE.txt +21 -21
- data/README.md +188 -179
- data/Rakefile +59 -59
- data/autowow.gemspec +42 -41
- data/bin/autowow +5 -5
- data/bin/aw +5 -5
- data/bin/console +14 -14
- data/bin/setup +8 -8
- data/lib/autowow.rb +13 -13
- data/lib/autowow/cli.rb +121 -115
- data/lib/autowow/commands/gem.rb +54 -54
- data/lib/autowow/commands/os.rb +11 -11
- data/lib/autowow/commands/rbenv.rb +19 -19
- data/lib/autowow/commands/vcs.rb +99 -95
- data/lib/autowow/decorators/string_decorator.rb +11 -11
- data/lib/autowow/executor.rb +100 -100
- data/lib/autowow/features/fs.rb +49 -49
- data/lib/autowow/features/gem.rb +138 -132
- data/lib/autowow/features/os.rb +16 -16
- data/lib/autowow/features/rbenv.rb +50 -50
- data/lib/autowow/features/vcs.rb +282 -272
- data/lib/autowow/log_formatter.rb +25 -25
- data/lib/autowow/time_difference.rb +29 -29
- data/lib/autowow/version.rb +3 -3
- metadata +17 -3
data/lib/autowow/features/vcs.rb
CHANGED
@@ -1,272 +1,282 @@
|
|
1
|
-
require "uri"
|
2
|
-
require "net/https"
|
3
|
-
require "net/http"
|
4
|
-
require "json"
|
5
|
-
require "launchy"
|
6
|
-
|
7
|
-
require_relative "../commands/vcs"
|
8
|
-
require_relative "fs"
|
9
|
-
require_relative "rbenv"
|
10
|
-
require_relative "gem"
|
11
|
-
require_relative "../time_difference"
|
12
|
-
|
13
|
-
module Autowow
|
14
|
-
module Features
|
15
|
-
module Vcs
|
16
|
-
include EasyLogging
|
17
|
-
include Commands::Vcs
|
18
|
-
include Executor
|
19
|
-
include StringDecorator
|
20
|
-
|
21
|
-
using RefinedTimeDifference
|
22
|
-
|
23
|
-
def self.hi!
|
24
|
-
logger.error("In a git repository. Try 1 level higher.") && return if is_git?
|
25
|
-
hi do
|
26
|
-
logger.info("Removing unused branches...")
|
27
|
-
clear_branches
|
28
|
-
logger.info("Adding upstream...")
|
29
|
-
add_upstream
|
30
|
-
logger.info("Removing unused gems...")
|
31
|
-
Gem.gem_clean
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.hi
|
36
|
-
logger.error("In a git repository. Try 1 level higher.") && return if is_git?
|
37
|
-
latest_project_info = get_latest_repo_info
|
38
|
-
logger.info("\nHang on, updating your local projects and remote forks...\n\n")
|
39
|
-
git_projects.each do |project|
|
40
|
-
Dir.chdir(project) do
|
41
|
-
logger.info("\nGetting #{project} in shape...")
|
42
|
-
yield if block_given?
|
43
|
-
update_project
|
44
|
-
end
|
45
|
-
end
|
46
|
-
greet(latest_project_info)
|
47
|
-
end
|
48
|
-
|
49
|
-
def
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
url =
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
logger.
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
logger.error("
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
109
|
-
|
110
|
-
def self.
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
pretty_with_output.run(branch)
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
pretty_with_output.run(git_status)
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
quiet.run(
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
1
|
+
require "uri"
|
2
|
+
require "net/https"
|
3
|
+
require "net/http"
|
4
|
+
require "json"
|
5
|
+
require "launchy"
|
6
|
+
|
7
|
+
require_relative "../commands/vcs"
|
8
|
+
require_relative "fs"
|
9
|
+
require_relative "rbenv"
|
10
|
+
require_relative "gem"
|
11
|
+
require_relative "../time_difference"
|
12
|
+
|
13
|
+
module Autowow
|
14
|
+
module Features
|
15
|
+
module Vcs
|
16
|
+
include EasyLogging
|
17
|
+
include Commands::Vcs
|
18
|
+
include Executor
|
19
|
+
include StringDecorator
|
20
|
+
|
21
|
+
using RefinedTimeDifference
|
22
|
+
|
23
|
+
def self.hi!
|
24
|
+
logger.error("In a git repository. Try 1 level higher.") && return if is_git?
|
25
|
+
hi do
|
26
|
+
logger.info("Removing unused branches...")
|
27
|
+
clear_branches
|
28
|
+
logger.info("Adding upstream...")
|
29
|
+
add_upstream
|
30
|
+
logger.info("Removing unused gems...")
|
31
|
+
Gem.gem_clean
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.hi
|
36
|
+
logger.error("In a git repository. Try 1 level higher.") && return if is_git?
|
37
|
+
latest_project_info = get_latest_repo_info
|
38
|
+
logger.info("\nHang on, updating your local projects and remote forks...\n\n")
|
39
|
+
git_projects.each do |project|
|
40
|
+
Dir.chdir(project) do
|
41
|
+
logger.info("\nGetting #{project} in shape...")
|
42
|
+
yield if block_given?
|
43
|
+
update_project
|
44
|
+
end
|
45
|
+
end
|
46
|
+
greet(latest_project_info)
|
47
|
+
end
|
48
|
+
|
49
|
+
def force_pull
|
50
|
+
pretty_with_output.run(git_status)
|
51
|
+
branch = working_branch
|
52
|
+
|
53
|
+
pretty_with_output.run(fetch("--all"))
|
54
|
+
pretty_with_output.run(hard_reset("origin/#{branch}"))
|
55
|
+
|
56
|
+
pretty_with_output.run(git_status)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.open
|
60
|
+
url = origin_push_url(quiet.run(remotes).out)
|
61
|
+
logger.info("Opening #{url}")
|
62
|
+
Launchy.open(url)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.add_upstream
|
66
|
+
logger.error("Not a git repository.") and return unless is_git?
|
67
|
+
logger.warn("Already has upstream.") and return if has_upstream?
|
68
|
+
remote_list = pretty_with_output.run(remotes).out
|
69
|
+
|
70
|
+
url = URI.parse(origin_push_url(remote_list))
|
71
|
+
host = "api.#{url.host}"
|
72
|
+
path = "/repos#{url.path}"
|
73
|
+
request = Net::HTTP.new(host, url.port)
|
74
|
+
request.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
75
|
+
request.use_ssl = url.scheme == "https"
|
76
|
+
logger.info("Fetching repo info from #{host}#{path}\n\n")
|
77
|
+
response = request.get(path)
|
78
|
+
|
79
|
+
if response.kind_of?(Net::HTTPRedirection)
|
80
|
+
logger.error("Repository moved / renamed. Update remote or implement redirect handling. :)")
|
81
|
+
elsif response.kind_of?(Net::HTTPNotFound)
|
82
|
+
logger.error("Repository not found. Maybe it is private.")
|
83
|
+
elsif response.kind_of?(Net::HTTPSuccess)
|
84
|
+
parsed_response = JSON.parse(response.body)
|
85
|
+
logger.warn("Not a fork.") and return unless parsed_response["fork"]
|
86
|
+
parent_url = parsed_response.dig("parent", "html_url")
|
87
|
+
pretty.run(add_remote("upstream", parent_url)) unless parent_url.to_s.empty?
|
88
|
+
pretty_with_output.run(remotes)
|
89
|
+
else
|
90
|
+
logger.error("Github API (#{url.scheme}://#{host}#{path}) could not be reached: #{response.body}")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.origin_push_url(remotes)
|
95
|
+
# Order is important: first try to match "url" in "#{url}.git" as non-dot_git matchers would include ".git" in the match
|
96
|
+
origin_push_url_ssl_dot_git(remotes) or
|
97
|
+
origin_push_url_ssl(remotes) or
|
98
|
+
origin_push_url_https_dot_git(remotes) or
|
99
|
+
origin_push_url_https(remotes)
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.origin_push_url_https(remotes)
|
103
|
+
remotes[%r{(?<=origin(\s))http(s?)://[a-zA-Z\-_./]*(?=(\s)\(push\))}]
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.origin_push_url_https_dot_git(remotes)
|
107
|
+
remotes[%r{(?<=origin(\s))http(s?)://[a-zA-Z\-_./]*(?=(\.)git(\s)\(push\))}]
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.origin_push_url_ssl_dot_git(remotes)
|
111
|
+
url = remotes[%r{(?<=origin(\s)git@)[a-zA-Z\-_./:]*(?=(\.)git(\s)\(push\))}]
|
112
|
+
"https://#{url.gsub(':', '/')}" if url
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.origin_push_url_ssl(remotes)
|
116
|
+
url = remotes[%r{(?<=origin(\s)git@)[a-zA-Z\-_./:]*(?=(\s)\(push\))}]
|
117
|
+
"https://#{url.gsub(':', '/')}" if url
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.clear_branches
|
121
|
+
pretty_with_output.run(branch)
|
122
|
+
branch_removed = false
|
123
|
+
|
124
|
+
(branches - ["master", working_branch]).each do |branch|
|
125
|
+
if branch_pushed(branch)
|
126
|
+
pretty.run(branch_force_delete(branch))
|
127
|
+
branch_removed = true
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
pretty_with_output.run(branch) if branch_removed
|
132
|
+
end
|
133
|
+
|
134
|
+
def update_projects
|
135
|
+
Fs.in_place_or_subdirs(is_git?) do
|
136
|
+
update_project
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def update_project
|
141
|
+
logger.info("Updating #{File.expand_path('.')} ...")
|
142
|
+
logger.error("Not a git repository.") and return unless is_git?
|
143
|
+
status = quiet.run(git_status).out
|
144
|
+
if uncommitted_changes?(status) && working_branch.eql?("master")
|
145
|
+
logger.warn("Skipped: uncommitted changes on master.") and return
|
146
|
+
end
|
147
|
+
|
148
|
+
on_branch("master") do
|
149
|
+
has_upstream? ? pull_upstream : pretty_with_output.run(pull)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def pull_upstream
|
154
|
+
upstream_remote = "upstream"
|
155
|
+
remote = "origin"
|
156
|
+
branch = "master"
|
157
|
+
pretty_with_output.run(fetch(upstream_remote)).out
|
158
|
+
pretty_with_output.run(merge("#{upstream_remote}/#{branch}")).out
|
159
|
+
pretty_with_output.run(push(remote, branch))
|
160
|
+
end
|
161
|
+
|
162
|
+
def has_upstream?
|
163
|
+
quiet.run(remotes).out.include?("upstream")
|
164
|
+
end
|
165
|
+
|
166
|
+
def on_branch(branch)
|
167
|
+
keep_changes do
|
168
|
+
start_branch = working_branch
|
169
|
+
switch_needed = !start_branch.eql?(branch)
|
170
|
+
if switch_needed
|
171
|
+
result = pretty.run!(checkout(branch))
|
172
|
+
pretty.run(create(branch)) unless result.success?
|
173
|
+
end
|
174
|
+
|
175
|
+
begin
|
176
|
+
yield if block_given?
|
177
|
+
ensure
|
178
|
+
pretty.run(checkout(start_branch)) if switch_needed
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def branch_merged
|
184
|
+
pretty_with_output.run(git_status)
|
185
|
+
branch = working_branch
|
186
|
+
logger.error("Nothing to do.") and return if branch.eql?("master")
|
187
|
+
|
188
|
+
keep_changes do
|
189
|
+
pretty_with_output.run(checkout("master"))
|
190
|
+
pretty_with_output.run(pull)
|
191
|
+
end
|
192
|
+
pretty_with_output.run(branch_force_delete(branch))
|
193
|
+
|
194
|
+
pretty_with_output.run(git_status)
|
195
|
+
end
|
196
|
+
|
197
|
+
def working_branch
|
198
|
+
quiet.run(current_branch).out.strip
|
199
|
+
end
|
200
|
+
|
201
|
+
def branch_pushed(branch)
|
202
|
+
quiet.run(changes_not_on_remote(branch)).out.empty?
|
203
|
+
end
|
204
|
+
|
205
|
+
def greet(latest_project_info = nil)
|
206
|
+
logger.info("\nGood morning!\n\n")
|
207
|
+
if is_git?
|
208
|
+
logger.error("Inside repo, cannot show report about all repos.")
|
209
|
+
else
|
210
|
+
latest_project_info ||= get_latest_repo_info
|
211
|
+
logger.info(latest_project_info)
|
212
|
+
check_projects_older_than(1, :months)
|
213
|
+
end
|
214
|
+
obsolete_rubies = Rbenv.obsolete_versions
|
215
|
+
if obsolete_rubies.any?
|
216
|
+
logger.info("\nThe following Ruby versions are not used by any projects, maybe consider removing them?")
|
217
|
+
obsolete_rubies.each do |ruby_verion|
|
218
|
+
logger.info(" #{ruby_verion}")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def check_projects_older_than(quantity, unit)
|
224
|
+
old_projects = Fs.older_than(git_projects, quantity, unit)
|
225
|
+
deprecated_projects = old_projects.reject do |project|
|
226
|
+
Dir.chdir(project) { branches.reject { |branch| branch_pushed(branch) }.any? }
|
227
|
+
end
|
228
|
+
|
229
|
+
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?
|
230
|
+
deprecated_projects.each do |project|
|
231
|
+
time_diff = TimeDifference.between(File.mtime(project), Time.now).humanize_higher_than(:weeks).downcase
|
232
|
+
logger.info(" #{File.basename(project)} (#{time_diff})")
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def get_latest_repo_info
|
237
|
+
latest = latest_repo
|
238
|
+
time_diff = TimeDifference.between(File.mtime(latest), Time.now).humanize_higher_than(:days).downcase
|
239
|
+
time_diff_text = time_diff.empty? ? "recently" : "#{time_diff} ago"
|
240
|
+
"It looks like you were working on #{File.basename(latest)} #{time_diff_text}.\n\n"
|
241
|
+
end
|
242
|
+
|
243
|
+
def latest_repo
|
244
|
+
Fs.latest(git_projects)
|
245
|
+
end
|
246
|
+
|
247
|
+
def branches
|
248
|
+
quiet.run(branch_list).out.clean_lines.map { |line| line[%r{(?<=refs/heads/)(.*)}] }
|
249
|
+
end
|
250
|
+
|
251
|
+
def uncommitted_changes?(status)
|
252
|
+
!(status.include?("nothing to commit, working tree clean") or status.include?("nothing added to commit but untracked files present"))
|
253
|
+
end
|
254
|
+
|
255
|
+
def keep_changes
|
256
|
+
status = quiet.run(git_status).out
|
257
|
+
pop_stash = uncommitted_changes?(status)
|
258
|
+
quiet.run(stash) if pop_stash
|
259
|
+
begin
|
260
|
+
yield if block_given?
|
261
|
+
ensure
|
262
|
+
quiet.run(stash_pop) if pop_stash
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def is_git?
|
267
|
+
status = quiet.run!(git_status)
|
268
|
+
Fs.git_folder_present && status.success? && !status.out.include?("Initial commit")
|
269
|
+
end
|
270
|
+
|
271
|
+
def git_projects
|
272
|
+
Fs.ls_dirs.select do |dir|
|
273
|
+
Dir.chdir(dir) do
|
274
|
+
is_git?
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
include ReflectionUtils::CreateModuleFunctions
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|