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, #add_remote, #branch, #branch_force_delete, #branch_list, #changes_not_on_remote, #checkout, #cmd, #commit, #create, #current_branch, #current_ref, #fetch, #git_status, #hard_reset, #merge, #pull, #push, #rebase, #remotes, #set_upstream, #show_ref, #stash, #stash_pop, #terminal_options, #upstream_tracking

Class Method Details

.add_upstreamObject



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/autowow/features/vcs.rb', line 69

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



124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/autowow/features/vcs.rb', line 124

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

.force_pullObject



53
54
55
56
57
58
59
60
61
# File 'lib/autowow/features/vcs.rb', line 53

def self.force_pull
  pretty_with_output.run(git_status)
  branch = working_branch

  pretty_with_output.run(fetch("--all"))
  pretty_with_output.run(hard_reset("origin/#{branch}"))

  pretty_with_output.run(git_status)
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



63
64
65
66
67
# File 'lib/autowow/features/vcs.rb', line 63

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

.origin_push_url(remotes) ⇒ Object



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

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



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

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



110
111
112
# File 'lib/autowow/features/vcs.rb', line 110

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



119
120
121
122
# File 'lib/autowow/features/vcs.rb', line 119

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



114
115
116
117
# File 'lib/autowow/features/vcs.rb', line 114

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

#any_branch_not_pushed?Boolean

Returns:

  • (Boolean)


247
248
249
# File 'lib/autowow/features/vcs.rb', line 247

def any_branch_not_pushed?
  branches.reject { |branch| branch_pushed(branch) }.any?
end

#branch_mergedObject



187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/autowow/features/vcs.rb', line 187

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



205
206
207
# File 'lib/autowow/features/vcs.rb', line 205

def branch_pushed(branch)
  is_tracked?(branch) && commits_pushed?(branch)
end

#branchesObject



262
263
264
# File 'lib/autowow/features/vcs.rb', line 262

def branches
  quiet.run(branch_list.join(" ")).out.clean_lines
end

#check_projects_older_than(quantity, unit) ⇒ Object



234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/autowow/features/vcs.rb', line 234

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) { any_branch_not_pushed? || uncommitted_changes?(quiet.run(git_status).out) }
  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

#commits_pushed?(branch) ⇒ Boolean

Returns:

  • (Boolean)


209
210
211
# File 'lib/autowow/features/vcs.rb', line 209

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

#get_latest_repo_infoObject



251
252
253
254
255
256
# File 'lib/autowow/features/vcs.rb', line 251

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



286
287
288
289
290
291
292
# File 'lib/autowow/features/vcs.rb', line 286

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

#greet(latest_project_info = nil) ⇒ Object



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/autowow/features/vcs.rb', line 213

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

  return unless Rbenv.exists?

  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)


166
167
168
# File 'lib/autowow/features/vcs.rb', line 166

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

#is_git?Boolean

Returns:

  • (Boolean)


281
282
283
284
# File 'lib/autowow/features/vcs.rb', line 281

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

#is_tracked?(branch) ⇒ Boolean

Returns:

  • (Boolean)


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

def is_tracked?(branch)
  !quiet.run!(Vcs.upstream_tracking(branch).join(" ")).out.strip.empty?
end

#keep_changesObject



270
271
272
273
274
275
276
277
278
279
# File 'lib/autowow/features/vcs.rb', line 270

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



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

def latest_repo
  Fs.latest(git_projects)
end

#on_branch(branch) ⇒ Object



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

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



157
158
159
160
161
162
163
164
# File 'lib/autowow/features/vcs.rb', line 157

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)


266
267
268
# File 'lib/autowow/features/vcs.rb', line 266

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

#update_projectObject



144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/autowow/features/vcs.rb', line 144

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



138
139
140
141
142
# File 'lib/autowow/features/vcs.rb', line 138

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

#working_branchObject



201
202
203
# File 'lib/autowow/features/vcs.rb', line 201

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