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.
@@ -0,0 +1,34 @@
1
+ require_relative '../commands/gem'
2
+ require_relative 'vcs'
3
+
4
+ module Autowow
5
+ module Features
6
+ module Gem
7
+ include EasyLogging
8
+ include Commands::Gem
9
+ include Commands::Vcs
10
+ include Executor
11
+
12
+ def gem_release
13
+ pretty_with_output.run(git_status)
14
+ start_branch = Vcs.working_branch
15
+ logger.error("Not on master.") and return unless start_branch.eql?('master')
16
+ pretty.run(push)
17
+
18
+ Vcs.on_branch('release') do
19
+ pretty.run(pull)
20
+ pretty.run(rebase(start_branch))
21
+ pretty_with_output.run(release)
22
+ end
23
+
24
+ pretty_with_output.run(git_status)
25
+ end
26
+
27
+ def gem_clean
28
+ pretty_with_output.run(clean)
29
+ end
30
+
31
+ include ReflectionUtils::CreateModuleFunctions
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ require_relative '../commands/os'
2
+
3
+ module Autowow
4
+ module Features
5
+ module Os
6
+ include Commands::Os
7
+ include Executor
8
+
9
+ def exists?(cmd)
10
+ quiet.run!(which(cmd)).success?
11
+ end
12
+
13
+ include ReflectionUtils::CreateModuleFunctions
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,48 @@
1
+ require_relative '../commands/rbenv'
2
+ require_relative '../commands/vcs'
3
+
4
+ require_relative 'fs'
5
+ require_relative 'vcs'
6
+
7
+ module Autowow
8
+ module Features
9
+ module Rbenv
10
+ include EasyLogging
11
+ include Commands::Rbenv
12
+ include Executor
13
+ include StringDecorator
14
+
15
+ def ruby_versions
16
+ logger.info(used_versions)
17
+ end
18
+
19
+ def used_versions
20
+ rubies = []
21
+ Fs.in_place_or_subdirs(Vcs.is_git?) do
22
+ result = quiet.run(version).out
23
+ rubies.concat(result.clean_lines)
24
+ end
25
+ rubies.uniq
26
+ end
27
+
28
+ def ruby_aliases
29
+ ret = {}
30
+ quiet.run(aliases).out.clean_lines.each do |line|
31
+ ret[line.split(' => ')[0]] = line.split(' => ')[1]
32
+ end
33
+ ret
34
+ end
35
+
36
+ def obsolete_versions
37
+ alias_map = ruby_aliases
38
+ used_versions_and_aliases = used_versions
39
+ used_versions.each do |v|
40
+ used_versions_and_aliases.push(alias_map[v]) if alias_map.has_key?(v)
41
+ end
42
+ quiet.run(installed_versions).out.clean_lines - used_versions_and_aliases
43
+ end
44
+
45
+ include ReflectionUtils::CreateModuleFunctions
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,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 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) and 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.run(pull)
140
+ end
141
+ end
142
+
143
+ def pull_upstream
144
+ upstream_remote = 'upstream'
145
+ remote = 'origin'
146
+ branch = 'master'
147
+ pretty.run(fetch(upstream_remote)).out
148
+ pretty.run(merge("#{upstream_remote}/#{branch}")).out
149
+ pretty.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.run(checkout('master'))
180
+ pretty.run(pull)
181
+ end
182
+ pretty.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
@@ -3,7 +3,7 @@ require 'colorize'
3
3
  module Autowow
4
4
  class LogFormatter
5
5
  def self.beautify(severity, msg)
6
- log_msg = msg.end_with?($/) ? msg : "#{msg}#{$/}"
6
+ log_msg = msg.to_s.end_with?($/) ? msg : "#{msg}#{$/}"
7
7
  log_msg = " $ #{log_msg}" if severity.eql?('DEBUG')
8
8
  color(severity, log_msg)
9
9
  end
@@ -1,3 +1,3 @@
1
1
  module Autowow
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: autowow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - thisismydesign
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-06 00:00:00.000000000 Z
11
+ date: 2017-11-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: easy_logging
@@ -80,6 +80,34 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: tty-command
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: reflection_utils
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 0.3.0
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 0.3.0
83
111
  - !ruby/object:Gem::Dependency
84
112
  name: bundler
85
113
  requirement: !ruby/object:Gem::Requirement
@@ -202,14 +230,19 @@ files:
202
230
  - bin/setup
203
231
  - lib/autowow.rb
204
232
  - lib/autowow/cli.rb
205
- - lib/autowow/command.rb
233
+ - lib/autowow/commands/gem.rb
234
+ - lib/autowow/commands/os.rb
235
+ - lib/autowow/commands/rbenv.rb
236
+ - lib/autowow/commands/vcs.rb
206
237
  - lib/autowow/decorators/string_decorator.rb
207
- - lib/autowow/fs.rb
208
- - lib/autowow/gem.rb
238
+ - lib/autowow/executor.rb
239
+ - lib/autowow/features/fs.rb
240
+ - lib/autowow/features/gem.rb
241
+ - lib/autowow/features/os.rb
242
+ - lib/autowow/features/rbenv.rb
243
+ - lib/autowow/features/vcs.rb
209
244
  - lib/autowow/log_formatter.rb
210
- - lib/autowow/ruby.rb
211
245
  - lib/autowow/time_difference.rb
212
- - lib/autowow/vcs.rb
213
246
  - lib/autowow/version.rb
214
247
  homepage: https://github.com/thisismydesign/autowow
215
248
  licenses: