Module: Autowow::Features::Vcs

Includes:
Commands::Vcs, Executor, EasyLogging, ReflectionUtils::CreateModuleFunctions, StringDecorator
Defined in:
lib/autowow/features/vcs.rb

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Executor

#pretty, #pretty_with_output, #quiet, #tty_params

Methods included from Commands::Vcs

#add_remote, #branch, #branch_force_delete, #branch_list, #changes_not_on_remote, #checkout, #cmd, #create, #current_branch, #fetch, #git_status, #merge, #pull, #push, #rebase, #remotes, #set_upstream, #stash, #stash_pop, #terminal_options

Class Method Details

.add_upstreamObject



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/autowow/features/vcs.rb', line 55

def self.add_upstream
  logger.error("Not a git repository.") and return unless is_git?
  logger.warn("Already has upstream.") and return if has_upstream?
  remote_list = pretty_with_output.run(remotes).out

  url = URI.parse(origin_push_url(remote_list))
  host = "api.#{url.host}"
  path = "/repos#{url.path}"
  request = Net::HTTP.new(host, url.port)
  request.verify_mode = OpenSSL::SSL::VERIFY_NONE
  request.use_ssl = url.scheme == "https"
  logger.info("Fetching repo info from #{host}#{path}\n\n")
  response = request.get(path)

  if response.kind_of?(Net::HTTPRedirection)
    logger.error("Repository moved / renamed. Update remote or implement redirect handling. :)")
  elsif response.kind_of?(Net::HTTPNotFound)
    logger.error("Repository not found. Maybe it is private.")
  elsif response.kind_of?(Net::HTTPSuccess)
    parsed_response = JSON.parse(response.body)
    logger.warn("Not a fork.") and return unless parsed_response["fork"]
    parent_url = parsed_response.dig("parent", "html_url")
    pretty.run(add_remote("upstream", parent_url)) unless parent_url.to_s.empty?
    pretty_with_output.run(remotes)
  else
    logger.error("Github API (#{url.scheme}://#{host}#{path}) could not be reached: #{response.body}")
  end
end

.clear_branchesObject



110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/autowow/features/vcs.rb', line 110

def self.clear_branches
  pretty_with_output.run(branch)
  branch_removed = false

  (branches - ["master", working_branch]).each do |branch|
    if branch_pushed(branch)
      pretty.run(branch_force_delete(branch))
      branch_removed = true
    end
  end

  pretty_with_output.run(branch) if branch_removed
end

.hiObject



35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/autowow/features/vcs.rb', line 35

def self.hi
  logger.error("In a git repository. Try 1 level higher.") && return if is_git?
  latest_project_info = get_latest_repo_info
  logger.info("\nHang on, updating your local projects and remote forks...\n\n")
  git_projects.each do |project|
    Dir.chdir(project) do
      logger.info("\nGetting #{project} in shape...")
      yield if block_given?
      update_project
    end
  end
  greet(latest_project_info)
end

.hi!Object



23
24
25
26
27
28
29
30
31
32
33
# File 'lib/autowow/features/vcs.rb', line 23

def self.hi!
  logger.error("In a git repository. Try 1 level higher.") && return if is_git?
  hi do
    logger.info("Removing unused branches...")
    clear_branches
    logger.info("Adding upstream...")
    add_upstream
    logger.info("Removing unused gems...")
    Gem.gem_clean
  end
end

.openObject



49
50
51
52
53
# File 'lib/autowow/features/vcs.rb', line 49

def self.open
  url = origin_push_url(quiet.run(remotes).out)
  logger.info("Opening #{url}")
  Launchy.open(url)
end

.origin_push_url(remotes) ⇒ Object



84
85
86
87
88
89
90
# File 'lib/autowow/features/vcs.rb', line 84

def self.origin_push_url(remotes)
  # Order is important: first try to match "url" in "#{url}.git" as non-dot_git matchers would include ".git" in the match
  origin_push_url_ssl_dot_git(remotes) or
    origin_push_url_ssl(remotes) or
    origin_push_url_https_dot_git(remotes) or
    origin_push_url_https(remotes)
end

.origin_push_url_https(remotes) ⇒ Object



92
93
94
# File 'lib/autowow/features/vcs.rb', line 92

def self.origin_push_url_https(remotes)
  remotes[%r{(?<=origin(\s))http(s?)://[a-zA-Z\-_./]*(?=(\s)\(push\))}]
end

.origin_push_url_https_dot_git(remotes) ⇒ Object



96
97
98
# File 'lib/autowow/features/vcs.rb', line 96

def self.origin_push_url_https_dot_git(remotes)
  remotes[%r{(?<=origin(\s))http(s?)://[a-zA-Z\-_./]*(?=(\.)git(\s)\(push\))}]
end

.origin_push_url_ssl(remotes) ⇒ Object



105
106
107
108
# File 'lib/autowow/features/vcs.rb', line 105

def self.origin_push_url_ssl(remotes)
  url = remotes[%r{(?<=origin(\s)git@)[a-zA-Z\-_./:]*(?=(\s)\(push\))}]
  "https://#{url.gsub(':', '/')}" if url
end

.origin_push_url_ssl_dot_git(remotes) ⇒ Object



100
101
102
103
# File 'lib/autowow/features/vcs.rb', line 100

def self.origin_push_url_ssl_dot_git(remotes)
  url = remotes[%r{(?<=origin(\s)git@)[a-zA-Z\-_./:]*(?=(\.)git(\s)\(push\))}]
  "https://#{url.gsub(':', '/')}" if url
end

Instance Method Details

#branch_mergedObject



173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/autowow/features/vcs.rb', line 173

def branch_merged
  pretty_with_output.run(git_status)
  branch = working_branch
  logger.error("Nothing to do.") and return if branch.eql?("master")

  keep_changes do
    pretty_with_output.run(checkout("master"))
    pretty_with_output.run(pull)
  end
  pretty_with_output.run(branch_force_delete(branch))

  pretty_with_output.run(git_status)
end

#branch_pushed(branch) ⇒ Object



191
192
193
# File 'lib/autowow/features/vcs.rb', line 191

def branch_pushed(branch)
  quiet.run(changes_not_on_remote(branch)).out.empty?
end

#branchesObject



237
238
239
# File 'lib/autowow/features/vcs.rb', line 237

def branches
  quiet.run(branch_list).out.clean_lines.map { |line| line[%r{(?<=refs/heads/)(.*)}] }
end

#check_projects_older_than(quantity, unit) ⇒ Object



213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/autowow/features/vcs.rb', line 213

def check_projects_older_than(quantity, unit)
  old_projects = Fs.older_than(git_projects, quantity, unit)
  deprecated_projects = old_projects.reject do |project|
    Dir.chdir(project) { branches.reject { |branch| branch_pushed(branch) }.any? }
  end

  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?
  deprecated_projects.each do |project|
    time_diff = TimeDifference.between(File.mtime(project), Time.now).humanize_higher_than(:weeks).downcase
    logger.info("  #{File.basename(project)} (#{time_diff})")
  end
end

#get_latest_repo_infoObject



226
227
228
229
230
231
# File 'lib/autowow/features/vcs.rb', line 226

def get_latest_repo_info
  latest = latest_repo
  time_diff = TimeDifference.between(File.mtime(latest), Time.now).humanize_higher_than(:days).downcase
  time_diff_text = time_diff.empty? ? "recently" : "#{time_diff} ago"
  "It looks like you were working on #{File.basename(latest)} #{time_diff_text}.\n\n"
end

#git_projectsObject



261
262
263
264
265
266
267
# File 'lib/autowow/features/vcs.rb', line 261

def git_projects
  Fs.ls_dirs.select do |dir|
    Dir.chdir(dir) do
      is_git?
    end
  end
end

#greet(latest_project_info = nil) ⇒ Object



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/autowow/features/vcs.rb', line 195

def greet(latest_project_info = nil)
  logger.info("\nGood morning!\n\n")
  if is_git?
    logger.error("Inside repo, cannot show report about all repos.")
  else
    latest_project_info ||= get_latest_repo_info
    logger.info(latest_project_info)
    check_projects_older_than(1, :months)
  end
  obsolete_rubies = Rbenv.obsolete_versions
  if obsolete_rubies.any?
    logger.info("\nThe following Ruby versions are not used by any projects, maybe consider removing them?")
    obsolete_rubies.each do |ruby_verion|
      logger.info("  #{ruby_verion}")
    end
  end
end

#has_upstream?Boolean

Returns:

  • (Boolean)


152
153
154
# File 'lib/autowow/features/vcs.rb', line 152

def has_upstream?
  quiet.run(remotes).out.include?("upstream")
end

#is_git?Boolean

Returns:

  • (Boolean)


256
257
258
259
# File 'lib/autowow/features/vcs.rb', line 256

def is_git?
  status = quiet.run!(git_status)
  Fs.git_folder_present && status.success? && !status.out.include?("Initial commit")
end

#keep_changesObject



245
246
247
248
249
250
251
252
253
254
# File 'lib/autowow/features/vcs.rb', line 245

def keep_changes
  status = quiet.run(git_status).out
  pop_stash = uncommitted_changes?(status)
  quiet.run(stash) if pop_stash
  begin
    yield if block_given?
  ensure
    quiet.run(stash_pop) if pop_stash
  end
end

#latest_repoObject



233
234
235
# File 'lib/autowow/features/vcs.rb', line 233

def latest_repo
  Fs.latest(git_projects)
end

#on_branch(branch) ⇒ Object



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/autowow/features/vcs.rb', line 156

def on_branch(branch)
  keep_changes do
    start_branch = working_branch
    switch_needed = !start_branch.eql?(branch)
    if switch_needed
      result = pretty.run!(checkout(branch))
      pretty.run(create(branch)) unless result.success?
    end

    begin
      yield if block_given?
    ensure
      pretty.run(checkout(start_branch)) if switch_needed
    end
  end
end

#pull_upstreamObject



143
144
145
146
147
148
149
150
# File 'lib/autowow/features/vcs.rb', line 143

def pull_upstream
  upstream_remote = "upstream"
  remote = "origin"
  branch = "master"
  pretty_with_output.run(fetch(upstream_remote)).out
  pretty_with_output.run(merge("#{upstream_remote}/#{branch}")).out
  pretty_with_output.run(push(remote, branch))
end

#uncommitted_changes?(status) ⇒ Boolean

Returns:

  • (Boolean)


241
242
243
# File 'lib/autowow/features/vcs.rb', line 241

def uncommitted_changes?(status)
  !(status.include?("nothing to commit, working tree clean") or status.include?("nothing added to commit but untracked files present"))
end

#update_projectObject



130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/autowow/features/vcs.rb', line 130

def update_project
  logger.info("Updating #{File.expand_path('.')} ...")
  logger.error("Not a git repository.") and return unless is_git?
  status = quiet.run(git_status).out
  if uncommitted_changes?(status) && working_branch.eql?("master")
    logger.warn("Skipped: uncommitted changes on master.") and return
  end

  on_branch("master") do
    has_upstream? ? pull_upstream : pretty_with_output.run(pull)
  end
end

#update_projectsObject



124
125
126
127
128
# File 'lib/autowow/features/vcs.rb', line 124

def update_projects
  Fs.in_place_or_subdirs(is_git?) do
    update_project
  end
end

#working_branchObject



187
188
189
# File 'lib/autowow/features/vcs.rb', line 187

def working_branch
  quiet.run(current_branch).out.strip
end