autowow 0.3.0 → 0.4.0

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