autowow 0.12.1 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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