Class: Autowow::Vcs

Inherits:
Object
  • Object
show all
Includes:
EasyLogging, StringDecorator
Defined in:
lib/autowow/vcs.rb

Class Method Summary collapse

Class Method Details

.add_remote(name, url) ⇒ Object



243
244
245
# File 'lib/autowow/vcs.rb', line 243

def self.add_remote(name, url)
  Command.run('git', 'remote', 'add', name, url)
end

.add_upstreamObject



65
66
67
68
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
# File 'lib/autowow/vcs.rb', line 65

def self.add_upstream
  start_status = status_dry
  logger.error("Not a git repository.") and return unless is_git?(start_status)
  remote_list = remotes.stdout
  logger.warn("Already has upstream.") and return if has_upstream?(remote_list)
  logger.info(remote_list)

  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'
  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')
    add_remote('upstream', parent_url) unless parent_url.to_s.empty?
    logger.info(remotes.stdout)
  else
    logger.error("Github API (#{url.scheme}://#{host}#{path}) could not be reached: #{response.body}")
  end
end

.branchObject



189
190
191
# File 'lib/autowow/vcs.rb', line 189

def self.branch
  Command.run('git', 'branch')
end

.branch_force_delete(branch) ⇒ Object



197
198
199
# File 'lib/autowow/vcs.rb', line 197

def self.branch_force_delete(branch)
  Command.run('git', 'branch', '-D', branch)
end

.branch_mergedObject



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

def self.branch_merged
  start_status = status
  logger.info(start_status)
  working_branch = current_branch
  logger.error("Nothing to do.") and return if working_branch.eql?('master')

  keep_changes do
    checkout('master')
    pull
  end
  branch_force_delete(working_branch)

  logger.info(status)
end

.branch_pushed(branch) ⇒ Object



278
279
280
# File 'lib/autowow/vcs.rb', line 278

def self.branch_pushed(branch)
  Command.run_dry('git', 'log', branch, '--not', '--remotes').stdout.empty?
end

.branchesObject



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

def self.branches
  branches = Command.run_dry('git', 'for-each-ref', "--format='%(refname)'", 'refs/heads/').stdout
  branches.each_line.map { |line| line.strip[%r{(?<='refs/heads/)(.*)(?=')}] }
end

.check_projects_older_than(quantity, unit) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/autowow/vcs.rb', line 139

def self.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

.checkout(existing_branch) ⇒ Object



170
171
172
# File 'lib/autowow/vcs.rb', line 170

def self.checkout(existing_branch)
  Command.run('git', 'checkout', existing_branch)
end

.clear_branchesObject



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

def self.clear_branches
  logger.info(branch.stdout)
  working_branch = current_branch
  master_branch = 'master'

  (branches - [master_branch, working_branch]).each do |branch|
    branch_force_delete(branch) if branch_pushed(branch)
  end

  logger.info(branch.stdout)
end

.create(branch) ⇒ Object



174
175
176
177
# File 'lib/autowow/vcs.rb', line 174

def self.create(branch)
  Command.run('git', 'checkout', '-b', branch)
  Command.run('git', 'push', '--set-upstream', 'origin', branch)
end

.current_branchObject



156
157
158
# File 'lib/autowow/vcs.rb', line 156

def self.current_branch
  Command.run_dry('git', 'symbolic-ref', '--short', 'HEAD').stdout
end

.get_latest_project_infoObject



128
129
130
131
132
133
# File 'lib/autowow/vcs.rb', line 128

def self.get_latest_project_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



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

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

.has_upstream?(remotes) ⇒ Boolean

Returns:

  • (Boolean)


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

def self.has_upstream?(remotes)
  remotes.include?('upstream')
end

.hiObject



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/autowow/vcs.rb', line 95

def self.hi
  logger.error("In a git repository. Try 1 level higher.") && return if is_git?(status_dry)
  latest_project_info = get_latest_project_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
  logger.info("\nGood morning!\n\n")
  logger.info(latest_project_info)
  check_projects_older_than(1, :months)
  logger.info("\nThe following Ruby versions are not used by any projects, maybe consider removing them?\n  #{Ruby.obsolete_versions.join("\n  ")}")
end

.hi!Object



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

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

.is_git?(start_status) ⇒ Boolean

Returns:

  • (Boolean)


213
214
215
# File 'lib/autowow/vcs.rb', line 213

def self.is_git?(start_status)
  !start_status.include?('Not a git repository')
end

.keep_changesObject



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

def self.keep_changes
  status = status_dry
  pop_stash = uncommitted_changes?(status)
  stash if pop_stash
  yield
  stash_pop if pop_stash
end

.latest_repoObject



135
136
137
# File 'lib/autowow/vcs.rb', line 135

def self.latest_repo
  Fs.latest(git_projects)
end

.on_branch(branch) ⇒ Object



247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/autowow/vcs.rb', line 247

def self.on_branch(branch)
  keep_changes do
    working_branch = current_branch
    switch_needed = !working_branch.eql?(branch)
    if switch_needed
      result = checkout(branch)
      create(branch) if result.stderr.eql?("error: pathspec '#{branch}' did not match any file(s) known to git.")
    end

    yield

    checkout(working_branch) if switch_needed
  end
end

.openObject



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

def self.open
  Launchy.open(origin_push_url(remotes.stdout))
end

.origin_push_url(remotes) ⇒ Object



217
218
219
220
221
222
223
# File 'lib/autowow/vcs.rb', line 217

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



225
226
227
# File 'lib/autowow/vcs.rb', line 225

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



229
230
231
# File 'lib/autowow/vcs.rb', line 229

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



238
239
240
241
# File 'lib/autowow/vcs.rb', line 238

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



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

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

.pullObject



179
180
181
# File 'lib/autowow/vcs.rb', line 179

def self.pull
  Command.run('git', 'pull')
end

.pull_upstreamObject



183
184
185
186
187
# File 'lib/autowow/vcs.rb', line 183

def self.pull_upstream
  Command.run('git', 'fetch', 'upstream')
  Command.run('git', 'merge', 'upstream/master')
  Command.run('git', 'push', 'origin', 'master')
end

.pushObject



287
288
289
# File 'lib/autowow/vcs.rb', line 287

def self.push
  Command.run('git', 'push')
end

.rebase(branch) ⇒ Object



291
292
293
# File 'lib/autowow/vcs.rb', line 291

def self.rebase(branch)
  Command.run('git', 'rebase', branch)
end

.remotesObject



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

def self.remotes
  Command.run_dry('git', 'remote', '-v')
end

.stashObject



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

def self.stash
  Command.run('git', 'stash').output_does_not_match?(%r{No local changes to save})
end

.stash_popObject



193
194
195
# File 'lib/autowow/vcs.rb', line 193

def self.stash_pop
  Command.run('git', 'stash', 'pop')
end

.statusObject



160
161
162
163
# File 'lib/autowow/vcs.rb', line 160

def self.status
  status = Command.run('git', 'status')
  status.stdout + status.stderr
end

.status_dryObject



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

def self.status_dry
  status = Command.run_dry('git', 'status')
  status.stdout + status.stderr
end

.uncommitted_changes?(start_status) ⇒ Boolean

Returns:

  • (Boolean)


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

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

.update_projectObject



42
43
44
45
46
47
48
49
50
51
# File 'lib/autowow/vcs.rb', line 42

def self.update_project
  start_status = status_dry
  return unless is_git?(start_status)
  logger.info("Updating #{File.expand_path('.')} ...")
  logger.warn("Skipped: uncommitted changes on master.") and return if uncommitted_changes?(start_status) and current_branch.eql?('master')

  on_branch('master') do
    has_upstream?(remotes.stdout) ? pull_upstream : pull
  end
end

.update_projectsObject



36
37
38
39
40
# File 'lib/autowow/vcs.rb', line 36

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