autowow 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,49 +1,49 @@
1
- require_relative "../time_difference"
2
-
3
- module Autowow
4
- module Features
5
- module Fs
6
- using RefinedTimeDifference
7
-
8
- def ls_dirs
9
- Dir.glob(File.expand_path("./*/")).select { |f| File.directory? f }
10
- end
11
-
12
- def latest(files)
13
- files.sort_by { |f| File.mtime(f) }.reverse!.first
14
- end
15
-
16
- def older_than(files, quantity, unit)
17
- files.select do |dir|
18
- TimeDifference.between(File.mtime(dir), Time.now).public_send("in_#{unit}") > quantity
19
- end
20
- end
21
-
22
- def for_dirs(dirs)
23
- dirs.each do |working_dir|
24
- # TODO: add handling of directories via extra param to popen3
25
- # https://stackoverflow.com/a/10148084/2771889
26
- Dir.chdir(working_dir) do
27
- yield working_dir
28
- end
29
- end
30
- end
31
-
32
- def in_place_or_subdirs(in_place)
33
- if in_place
34
- yield
35
- else
36
- for_dirs(ls_dirs) do
37
- yield
38
- end
39
- end
40
- end
41
-
42
- def git_folder_present
43
- File.exist?(".git")
44
- end
45
-
46
- include ReflectionUtils::CreateModuleFunctions
47
- end
48
- end
49
- end
1
+ require_relative "../time_difference"
2
+
3
+ module Autowow
4
+ module Features
5
+ module Fs
6
+ using RefinedTimeDifference
7
+
8
+ def ls_dirs
9
+ Dir.glob(File.expand_path("./*/")).select { |f| File.directory? f }
10
+ end
11
+
12
+ def latest(files)
13
+ files.sort_by { |f| File.mtime(f) }.reverse!.first
14
+ end
15
+
16
+ def older_than(files, quantity, unit)
17
+ files.select do |dir|
18
+ TimeDifference.between(File.mtime(dir), Time.now).public_send("in_#{unit}") > quantity
19
+ end
20
+ end
21
+
22
+ def for_dirs(dirs)
23
+ dirs.each do |working_dir|
24
+ # TODO: add handling of directories via extra param to popen3
25
+ # https://stackoverflow.com/a/10148084/2771889
26
+ Dir.chdir(working_dir) do
27
+ yield working_dir
28
+ end
29
+ end
30
+ end
31
+
32
+ def in_place_or_subdirs(in_place)
33
+ if in_place
34
+ yield
35
+ else
36
+ for_dirs(ls_dirs) do
37
+ yield
38
+ end
39
+ end
40
+ end
41
+
42
+ def git_folder_present
43
+ File.exist?(".git")
44
+ end
45
+
46
+ include ReflectionUtils::CreateModuleFunctions
47
+ end
48
+ end
49
+ end
@@ -1,52 +1,52 @@
1
- require "pastel"
2
-
3
- require_relative "../commands/gem"
4
- require_relative "vcs"
5
-
6
- module Autowow
7
- module Features
8
- module Gem
9
- include EasyLogging
10
- include Commands::Gem
11
- include Commands::Vcs
12
- include Executor
13
-
14
- def gem_release
15
- pretty_with_output.run(git_status)
16
- start_branch = Vcs.working_branch
17
- logger.error("Not on master.") and return unless start_branch.eql?("master")
18
- pretty.run(push)
19
-
20
- Vcs.on_branch("release") do
21
- pretty.run(pull)
22
- pretty.run(rebase(start_branch))
23
- pretty_with_output.run(release)
24
- end
25
-
26
- pretty_with_output.run(git_status)
27
- end
28
-
29
- def gem_clean
30
- pretty_with_output.run(clean)
31
- end
32
-
33
- def rubocop_parallel_autocorrect
34
- pastel = Pastel.new
35
- result = pretty_with_output.run!(rubocop_parallel)
36
- if result.failed?
37
- filtered = result.out.each_line.select { |line| line.match(%r{(.*):([0-9]*):([0-9]*):}) }
38
- .map { |line| line.split(":")[0] }
39
- .uniq
40
- .map { |line| pastel.strip(line) }
41
- pretty_with_output.run(rubocop_autocorrect(filtered)) if filtered.any?
42
- end
43
- end
44
-
45
- def bundle_exec(cmd)
46
- Autowow::Executor.pretty_with_output.run(["bundle", "exec"] + cmd)
47
- end
48
-
49
- include ReflectionUtils::CreateModuleFunctions
50
- end
51
- end
52
- end
1
+ require "pastel"
2
+
3
+ require_relative "../commands/gem"
4
+ require_relative "vcs"
5
+
6
+ module Autowow
7
+ module Features
8
+ module Gem
9
+ include EasyLogging
10
+ include Commands::Gem
11
+ include Commands::Vcs
12
+ include Executor
13
+
14
+ def gem_release
15
+ pretty_with_output.run(git_status)
16
+ start_branch = Vcs.working_branch
17
+ logger.error("Not on master.") and return unless start_branch.eql?("master")
18
+ pretty.run(push)
19
+
20
+ Vcs.on_branch("release") do
21
+ pretty.run(pull)
22
+ pretty.run(rebase(start_branch))
23
+ pretty_with_output.run(release)
24
+ end
25
+
26
+ pretty_with_output.run(git_status)
27
+ end
28
+
29
+ def gem_clean
30
+ pretty_with_output.run(clean)
31
+ end
32
+
33
+ def rubocop_parallel_autocorrect
34
+ pastel = Pastel.new
35
+ result = pretty_with_output.run!(rubocop_parallel)
36
+ if result.failed?
37
+ filtered = result.out.each_line.select { |line| line.match(%r{(.*):([0-9]*):([0-9]*):}) }
38
+ .map { |line| line.split(":")[0] }
39
+ .uniq
40
+ .map { |line| pastel.strip(line) }
41
+ pretty_with_output.run(rubocop_autocorrect(filtered)) if filtered.any?
42
+ end
43
+ end
44
+
45
+ def bundle_exec(cmd)
46
+ Autowow::Executor.pretty_with_output.run(["bundle", "exec"] + cmd)
47
+ end
48
+
49
+ include ReflectionUtils::CreateModuleFunctions
50
+ end
51
+ end
52
+ end
@@ -1,16 +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
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
@@ -1,50 +1,50 @@
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)
23
- rubies.concat(result.out.clean_lines) if result.success?
24
- end
25
- rubies.uniq
26
- end
27
-
28
- def ruby_aliases
29
- ret = {}
30
- result = quiet.run!(aliases)
31
- return ret unless result.success?
32
- result.out.clean_lines.each do |line|
33
- ret[line.split(" => ")[0]] = line.split(" => ")[1]
34
- end
35
- ret
36
- end
37
-
38
- def obsolete_versions
39
- alias_map = ruby_aliases
40
- used_versions_and_aliases = used_versions
41
- used_versions.each do |v|
42
- used_versions_and_aliases.push(alias_map[v]) if alias_map.has_key?(v)
43
- end
44
- quiet.run(installed_versions).out.clean_lines - used_versions_and_aliases
45
- end
46
-
47
- include ReflectionUtils::CreateModuleFunctions
48
- end
49
- end
50
- end
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)
23
+ rubies.concat(result.out.clean_lines) if result.success?
24
+ end
25
+ rubies.uniq
26
+ end
27
+
28
+ def ruby_aliases
29
+ ret = {}
30
+ result = quiet.run!(aliases)
31
+ return ret unless result.success?
32
+ result.out.clean_lines.each do |line|
33
+ ret[line.split(" => ")[0]] = line.split(" => ")[1]
34
+ end
35
+ ret
36
+ end
37
+
38
+ def obsolete_versions
39
+ alias_map = ruby_aliases
40
+ used_versions_and_aliases = used_versions
41
+ used_versions.each do |v|
42
+ used_versions_and_aliases.push(alias_map[v]) if alias_map.has_key?(v)
43
+ end
44
+ quiet.run(installed_versions).out.clean_lines - used_versions_and_aliases
45
+ end
46
+
47
+ include ReflectionUtils::CreateModuleFunctions
48
+ end
49
+ end
50
+ end
@@ -1,272 +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) && 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 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