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.
@@ -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 self.open
50
- url = origin_push_url(quiet.run(remotes).out)
51
- logger.info("Opening #{url}")
52
- Launchy.open(url)
53
- end
54
-
55
- def self.add_upstream
56
- logger.error("Not a git repository.") and return unless is_git?
57
- logger.warn("Already has upstream.") and return if has_upstream?
58
- remote_list = pretty_with_output.run(remotes).out
59
-
60
- url = URI.parse(origin_push_url(remote_list))
61
- host = "api.#{url.host}"
62
- path = "/repos#{url.path}"
63
- request = Net::HTTP.new(host, url.port)
64
- request.verify_mode = OpenSSL::SSL::VERIFY_NONE
65
- request.use_ssl = url.scheme == "https"
66
- logger.info("Fetching repo info from #{host}#{path}\n\n")
67
- response = request.get(path)
68
-
69
- if response.kind_of?(Net::HTTPRedirection)
70
- logger.error("Repository moved / renamed. Update remote or implement redirect handling. :)")
71
- elsif response.kind_of?(Net::HTTPNotFound)
72
- logger.error("Repository not found. Maybe it is private.")
73
- elsif response.kind_of?(Net::HTTPSuccess)
74
- parsed_response = JSON.parse(response.body)
75
- logger.warn("Not a fork.") and return unless parsed_response["fork"]
76
- parent_url = parsed_response.dig("parent", "html_url")
77
- pretty.run(add_remote("upstream", parent_url)) unless parent_url.to_s.empty?
78
- pretty_with_output.run(remotes)
79
- else
80
- logger.error("Github API (#{url.scheme}://#{host}#{path}) could not be reached: #{response.body}")
81
- end
82
- end
83
-
84
- def self.origin_push_url(remotes)
85
- # Order is important: first try to match "url" in "#{url}.git" as non-dot_git matchers would include ".git" in the match
86
- origin_push_url_ssl_dot_git(remotes) or
87
- origin_push_url_ssl(remotes) or
88
- origin_push_url_https_dot_git(remotes) or
89
- origin_push_url_https(remotes)
90
- end
91
-
92
- def self.origin_push_url_https(remotes)
93
- remotes[%r{(?<=origin(\s))http(s?)://[a-zA-Z\-_./]*(?=(\s)\(push\))}]
94
- end
95
-
96
- def self.origin_push_url_https_dot_git(remotes)
97
- remotes[%r{(?<=origin(\s))http(s?)://[a-zA-Z\-_./]*(?=(\.)git(\s)\(push\))}]
98
- end
99
-
100
- def self.origin_push_url_ssl_dot_git(remotes)
101
- url = remotes[%r{(?<=origin(\s)git@)[a-zA-Z\-_./:]*(?=(\.)git(\s)\(push\))}]
102
- "https://#{url.gsub(':', '/')}" if url
103
- end
104
-
105
- def self.origin_push_url_ssl(remotes)
106
- url = remotes[%r{(?<=origin(\s)git@)[a-zA-Z\-_./:]*(?=(\s)\(push\))}]
107
- "https://#{url.gsub(':', '/')}" if url
108
- end
109
-
110
- def self.clear_branches
111
- pretty_with_output.run(branch)
112
- branch_removed = false
113
-
114
- (branches - ["master", working_branch]).each do |branch|
115
- if branch_pushed(branch)
116
- pretty.run(branch_force_delete(branch))
117
- branch_removed = true
118
- end
119
- end
120
-
121
- pretty_with_output.run(branch) if branch_removed
122
- end
123
-
124
- def update_projects
125
- Fs.in_place_or_subdirs(is_git?) do
126
- update_project
127
- end
128
- end
129
-
130
- def update_project
131
- logger.info("Updating #{File.expand_path('.')} ...")
132
- logger.error("Not a git repository.") and return unless is_git?
133
- status = quiet.run(git_status).out
134
- if uncommitted_changes?(status) && working_branch.eql?("master")
135
- logger.warn("Skipped: uncommitted changes on master.") and return
136
- end
137
-
138
- on_branch("master") do
139
- has_upstream? ? pull_upstream : pretty_with_output.run(pull)
140
- end
141
- end
142
-
143
- def pull_upstream
144
- upstream_remote = "upstream"
145
- remote = "origin"
146
- branch = "master"
147
- pretty_with_output.run(fetch(upstream_remote)).out
148
- pretty_with_output.run(merge("#{upstream_remote}/#{branch}")).out
149
- pretty_with_output.run(push(remote, branch))
150
- end
151
-
152
- def has_upstream?
153
- quiet.run(remotes).out.include?("upstream")
154
- end
155
-
156
- def on_branch(branch)
157
- keep_changes do
158
- start_branch = working_branch
159
- switch_needed = !start_branch.eql?(branch)
160
- if switch_needed
161
- result = pretty.run!(checkout(branch))
162
- pretty.run(create(branch)) unless result.success?
163
- end
164
-
165
- begin
166
- yield if block_given?
167
- ensure
168
- pretty.run(checkout(start_branch)) if switch_needed
169
- end
170
- end
171
- end
172
-
173
- def branch_merged
174
- pretty_with_output.run(git_status)
175
- branch = working_branch
176
- logger.error("Nothing to do.") and return if branch.eql?("master")
177
-
178
- keep_changes do
179
- pretty_with_output.run(checkout("master"))
180
- pretty_with_output.run(pull)
181
- end
182
- pretty_with_output.run(branch_force_delete(branch))
183
-
184
- pretty_with_output.run(git_status)
185
- end
186
-
187
- def working_branch
188
- quiet.run(current_branch).out.strip
189
- end
190
-
191
- def branch_pushed(branch)
192
- quiet.run(changes_not_on_remote(branch)).out.empty?
193
- end
194
-
195
- def greet(latest_project_info = nil)
196
- logger.info("\nGood morning!\n\n")
197
- if is_git?
198
- logger.error("Inside repo, cannot show report about all repos.")
199
- else
200
- latest_project_info ||= get_latest_repo_info
201
- logger.info(latest_project_info)
202
- check_projects_older_than(1, :months)
203
- end
204
- obsolete_rubies = Rbenv.obsolete_versions
205
- if obsolete_rubies.any?
206
- logger.info("\nThe following Ruby versions are not used by any projects, maybe consider removing them?")
207
- obsolete_rubies.each do |ruby_verion|
208
- logger.info(" #{ruby_verion}")
209
- end
210
- end
211
- end
212
-
213
- def check_projects_older_than(quantity, unit)
214
- old_projects = Fs.older_than(git_projects, quantity, unit)
215
- deprecated_projects = old_projects.reject do |project|
216
- Dir.chdir(project) { branches.reject { |branch| branch_pushed(branch) }.any? }
217
- end
218
-
219
- 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?
220
- deprecated_projects.each do |project|
221
- time_diff = TimeDifference.between(File.mtime(project), Time.now).humanize_higher_than(:weeks).downcase
222
- logger.info(" #{File.basename(project)} (#{time_diff})")
223
- end
224
- end
225
-
226
- def get_latest_repo_info
227
- latest = latest_repo
228
- time_diff = TimeDifference.between(File.mtime(latest), Time.now).humanize_higher_than(:days).downcase
229
- time_diff_text = time_diff.empty? ? "recently" : "#{time_diff} ago"
230
- "It looks like you were working on #{File.basename(latest)} #{time_diff_text}.\n\n"
231
- end
232
-
233
- def latest_repo
234
- Fs.latest(git_projects)
235
- end
236
-
237
- def branches
238
- quiet.run(branch_list).out.clean_lines.map { |line| line[%r{(?<=refs/heads/)(.*)}] }
239
- end
240
-
241
- def uncommitted_changes?(status)
242
- !(status.include?("nothing to commit, working tree clean") or status.include?("nothing added to commit but untracked files present"))
243
- end
244
-
245
- def keep_changes
246
- status = quiet.run(git_status).out
247
- pop_stash = uncommitted_changes?(status)
248
- quiet.run(stash) if pop_stash
249
- begin
250
- yield if block_given?
251
- ensure
252
- quiet.run(stash_pop) if pop_stash
253
- end
254
- end
255
-
256
- def is_git?
257
- status = quiet.run!(git_status)
258
- Fs.git_folder_present && status.success? && !status.out.include?("Initial commit")
259
- end
260
-
261
- def git_projects
262
- Fs.ls_dirs.select do |dir|
263
- Dir.chdir(dir) do
264
- is_git?
265
- end
266
- end
267
- end
268
-
269
- include ReflectionUtils::CreateModuleFunctions
270
- end
271
- end
272
- end
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