Module: SugarJar::Util

Included in:
Commands, RepoConfig
Defined in:
lib/sugarjar/util.rb

Overview

Some common methods needed by other classes

Instance Method Summary collapse

Instance Method Details

#all_local_branchesObject



160
161
162
163
164
165
166
167
168
# File 'lib/sugarjar/util.rb', line 160

def all_local_branches
  git(
    'branch', '--format', '%(refname)'
  ).stdout.lines.map do |line|
    next if line.start_with?('(HEAD detached')

    branch_from_ref(line.strip)
  end
end

#all_remote_branches(remote = 'origin') ⇒ Object



150
151
152
153
154
155
156
157
158
# File 'lib/sugarjar/util.rb', line 150

def all_remote_branches(remote = 'origin')
  branches = []
  git('branch', '-r', '--format', '%(refname)').stdout.lines.each do |line|
    next unless line.start_with?("refs/remotes/#{remote}/")

    branches << branch_from_ref(line.strip, :remote)
  end
  branches
end

#all_remotesObject



170
171
172
# File 'lib/sugarjar/util.rb', line 170

def all_remotes
  git('remote').stdout.lines.map(&:strip)
end

#assert_common_main_branchObject



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/sugarjar/util.rb', line 101

def assert_common_main_branch
  upstream_branch = main_remote_branch(upstream)
  unless main_branch == upstream_branch
    die(
      "The local main branch is '#{main_branch}', but the main branch " +
      "of the #{upstream} remote is '#{upstream_branch}'. You probably " +
      "want to rename your local branch by doing:\n\t" +
      "git branch -m #{main_branch} #{upstream_branch}\n\t" +
      "git fetch #{upstream}\n\t" +
      "git branch -u #{upstream}/#{upstream_branch} #{upstream_branch}\n" +
      "\tgit remote set-head #{upstream} -a",
    )
  end
  return if upstream_branch == 'origin'

  origin_branch = main_remote_branch('origin')
  return if origin_branch == upstream_branch

  die(
    "The main branch of your upstream (#{upstream_branch}) and your " +
    "fork/origin (#{origin_branch}) are not the same. You should go " +
    "to https://#{@ghhost || 'github.com'}/#{@ghuser}/#{repo_name}/" +
    'branches/ and rename the \'default\' branch to ' +
    "'#{upstream_branch}'. It will then give you some commands to " +
    'run to update this clone.',
  )
end

#assert_in_repoObject



129
130
131
# File 'lib/sugarjar/util.rb', line 129

def assert_in_repo
  die('sugarjar must be run from inside a git repo') unless in_repo
end

#branch_from_ref(ref, type = :local) ⇒ Object



245
246
247
248
249
250
# File 'lib/sugarjar/util.rb', line 245

def branch_from_ref(ref, type = :local)
  # local branches are refs/head/XXXX
  # remote branches are refs/remotes/<remote>/XXXX
  base = type == :local ? 2 : 3
  ref.split('/')[base..].join('/')
end

#canonicalize_repo(repo) ⇒ Object

gh utils will default to https, but we should always default to SSH unless otherwise specified since https will cause prompting.



34
35
36
37
38
39
40
41
42
# File 'lib/sugarjar/util.rb', line 34

def canonicalize_repo(repo)
  # if they fully-qualified it, we're good
  return repo if repo.start_with?('http', 'git@')

  # otherwise, ti's a shortname
  cr = "git@#{@ghhost || 'github.com'}:#{repo}.git"
  SugarJar::Log.debug("canonicalized #{repo} to #{cr}")
  cr
end

#checkout_main_branchObject



146
147
148
# File 'lib/sugarjar/util.rb', line 146

def checkout_main_branch
  git('checkout', main_branch)
end

#color(string, *colors) ⇒ Object



252
253
254
255
256
257
258
# File 'lib/sugarjar/util.rb', line 252

def color(string, *colors)
  if @color
    pastel.decorate(string, *colors)
  else
    string
  end
end

#current_branchObject



174
175
176
# File 'lib/sugarjar/util.rb', line 174

def current_branch
  branch_from_ref(git('symbolic-ref', 'HEAD').stdout.strip)
end

#determine_main_branch(branches) ⇒ Object



133
134
135
# File 'lib/sugarjar/util.rb', line 133

def determine_main_branch(branches)
  branches.include?('main') ? 'main' : 'master'
end

#die(msg) ⇒ Object



96
97
98
99
# File 'lib/sugarjar/util.rb', line 96

def die(msg)
  SugarJar::Log.fatal(msg)
  exit(1)
end

#dirty?Boolean

Returns:

  • (Boolean)


361
362
363
364
# File 'lib/sugarjar/util.rb', line 361

def dirty?
  s = git_nofail('diff', '--quiet')
  s.error?
end

#extract_org(repo) ⇒ Object



8
9
10
11
12
13
14
15
16
17
# File 'lib/sugarjar/util.rb', line 8

def extract_org(repo)
  if repo.start_with?('http')
    File.basename(File.dirname(repo))
  elsif repo.start_with?('git@')
    repo.split(':')[1].split('/')[0]
  else
    # assume they passed in a ghcli-friendly name
    repo.split('/').first
  end
end

#extract_repo(repo) ⇒ Object



19
20
21
# File 'lib/sugarjar/util.rb', line 19

def extract_repo(repo)
  File.basename(repo, '.git')
end

#fetch(remote) ⇒ Object



183
184
185
# File 'lib/sugarjar/util.rb', line 183

def fetch(remote)
  git('fetch', remote)
end

#fetch_upstreamObject



178
179
180
181
# File 'lib/sugarjar/util.rb', line 178

def fetch_upstream
  us = upstream
  fetch(us) if us
end

#forked_repo(repo, username) ⇒ Object



23
24
25
26
27
28
29
30
# File 'lib/sugarjar/util.rb', line 23

def forked_repo(repo, username)
  repo = if repo.start_with?('http', 'git@')
           File.basename(repo)
         else
           "#{File.basename(repo)}.git"
         end
  "git@#{@ghhost || 'github.com'}:#{username}/#{repo}"
end

#fprefix(name) ⇒ Object



271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/sugarjar/util.rb', line 271

def fprefix(name)
  return name unless @feature_prefix

  return name if name.start_with?(@feature_prefix)
  return name if all_local_branches.include?(name)

  newname = "#{@feature_prefix}#{name}"
  SugarJar::Log.debug(
    "Munging feature name: #{name} -> #{newname} due to feature prefix",
  )
  newname
end

#gh_avail?Boolean

Returns:

  • (Boolean)


267
268
269
# File 'lib/sugarjar/util.rb', line 267

def gh_avail?
  !!which_nofail('gh')
end

#ghcli(*args) ⇒ Object



350
351
352
353
354
# File 'lib/sugarjar/util.rb', line 350

def ghcli(*args)
  s = ghcli_nofail(*args)
  s.error!
  s
end

#ghcli_nofail(*args) ⇒ Object



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/sugarjar/util.rb', line 325

def ghcli_nofail(*args)
  SugarJar::Log.trace("Running: gh #{args.join(' ')}")
  s = Mixlib::ShellOut.new([which('gh')] + args).run_command
  if s.error? && s.stderr.include?('gh auth')
    SugarJar::Log.info(
      'gh was run but no github token exists. Will run "gh auth login" ' +
      "to force\ngh to authenticate...",
    )
    ENV['GITHUB_HOST'] = @ghhost if @ghhost
    args = [
      which('gh'), 'auth', 'login', '-p', 'ssh'
    ]
    args + ['--hostname', @ghhost] if @ghhost
    unless system(which('gh'), 'auth', 'login', '-p', 'ssh')
      SugarJar::Log.fatal(
        'That failed, I will bail out. Hub needs to get a github ' +
        'token. Try running "gh auth login" (will list info about ' +
        'your account) and try this again when that works.',
      )
      exit(1)
    end
  end
  s
end

#git(*args) ⇒ Object



319
320
321
322
323
# File 'lib/sugarjar/util.rb', line 319

def git(*args)
  s = git_nofail(*args)
  s.error!
  s
end

#git_nofail(*args) ⇒ Object



310
311
312
313
314
315
316
317
# File 'lib/sugarjar/util.rb', line 310

def git_nofail(*args)
  if %w{diff log grep branch}.include?(args[0]) &&
     args.none? { |x| x.include?('color') }
    args << (@color ? '--color' : '--no-color')
  end
  SugarJar::Log.trace("Running: git #{args.join(' ')}")
  Mixlib::ShellOut.new([which('git')] + args).run_command
end

#in_repoObject



356
357
358
359
# File 'lib/sugarjar/util.rb', line 356

def in_repo
  s = git_nofail('rev-parse', '--is-inside-work-tree')
  !s.error? && s.stdout.strip == 'true'
end

#main_branchObject



137
138
139
# File 'lib/sugarjar/util.rb', line 137

def main_branch
  @main_branch = determine_main_branch(all_local_branches)
end

#main_remote_branch(remote) ⇒ Object



141
142
143
144
# File 'lib/sugarjar/util.rb', line 141

def main_remote_branch(remote)
  @main_remote_branches[remote] ||=
    determine_main_branch(all_remote_branches(remote))
end

#most_mainObject



211
212
213
214
215
216
217
218
# File 'lib/sugarjar/util.rb', line 211

def most_main
  us = upstream
  if us
    "#{us}/#{main_branch}"
  else
    main_branch
  end
end

#pastelObject



260
261
262
263
264
265
# File 'lib/sugarjar/util.rb', line 260

def pastel
  @pastel ||= begin
    require 'pastel'
    Pastel.new
  end
end

#push_orgObject

Whatever org we push to, regardless of if this is a fork or not



240
241
242
243
# File 'lib/sugarjar/util.rb', line 240

def push_org
  url = git('remote', 'get-url', 'origin').stdout.strip
  extract_org(url)
end

#repo_nameObject



370
371
372
# File 'lib/sugarjar/util.rb', line 370

def repo_name
  repo_root.split('/').last
end

#repo_rootObject



366
367
368
# File 'lib/sugarjar/util.rb', line 366

def repo_root
  git('rev-parse', '--show-toplevel').stdout.strip
end

#run_prepushObject



85
86
87
88
89
90
91
92
93
94
# File 'lib/sugarjar/util.rb', line 85

def run_prepush
  @repo_config['on_push']&.each do |item|
    SugarJar::Log.debug("Running on_push check type #{item}")
    unless send(:run_check, item)
      SugarJar::Log.info("[prepush]: #{item} #{color('failed', :red)}.")
      return false
    end
  end
  true
end

#set_commit_templateObject



44
45
46
47
48
49
50
51
52
53
54
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
83
# File 'lib/sugarjar/util.rb', line 44

def set_commit_template
  unless in_repo
    SugarJar::Log.debug('Skipping set_commit_template: not in repo')
    return
  end

  realpath = if @repo_config['commit_template'].start_with?('/')
               @repo_config['commit_template']
             else
               "#{repo_root}/#{@repo_config['commit_template']}"
             end
  unless File.exist?(realpath)
    die(
      "Repo config specifies #{@repo_config['commit_template']} as the " +
      'commit template, but that file does not exist.',
    )
  end

  s = git_nofail('config', '--local', 'commit.template')
  unless s.error?
    current = s.stdout.strip
    if current == @repo_config['commit_template']
      SugarJar::Log.debug('Commit template already set correctly')
      return
    else
      SugarJar::Log.warn(
        "Updating repo-specific commit template from #{current} " +
        "to #{@repo_config['commit_template']}",
      )
    end
  end

  SugarJar::Log.debug(
    'Setting repo-specific commit template to ' +
    "#{@repo_config['commit_template']} per sugarjar repo config.",
  )
  git(
    'config', '--local', 'commit.template', @repo_config['commit_template']
  )
end

#subfeature?(base) ⇒ Boolean

determine if this branch is based on another local branch (i.e. is a subfeature). Used to figure out of we should stack the PR

Returns:

  • (Boolean)


189
190
191
# File 'lib/sugarjar/util.rb', line 189

def subfeature?(base)
  all_local_branches.reject { |x| x == most_main }.include?(base)
end

#tracked_branch(fallback: true) ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/sugarjar/util.rb', line 193

def tracked_branch(fallback: true)
  branch = nil
  s = git_nofail(
    'rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'
  )
  if s.error?
    branch = fallback ? most_main : nil
    SugarJar::Log.debug("No specific tracked branch, using #{branch}")
  else
    branch = s.stdout.strip
    SugarJar::Log.debug(
      "Using explicit tracked branch: #{branch}, use " +
      '`git branch -u` to change',
    )
  end
  branch
end

#upstreamObject



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/sugarjar/util.rb', line 220

def upstream
  return @remote if @remote

  remotes = all_remotes
  SugarJar::Log.debug("remotes is #{remotes}")
  if remotes.empty?
    @remote = nil
  elsif remotes.length == 1
    @remote = remotes[0]
  elsif remotes.include?('upstream')
    @remote = 'upstream'
  elsif remotes.include?('origin')
    @remote = 'origin'
  else
    raise 'Could not determine "upstream" remote to use...'
  end
  @remote
end

#which(cmd) ⇒ Object



302
303
304
305
306
307
308
# File 'lib/sugarjar/util.rb', line 302

def which(cmd)
  path = which_nofail(cmd)
  return path if path

  SugarJar::Log.fatal("Could not find #{cmd} in your path")
  exit(1)
end

#which_nofail(cmd) ⇒ Object

Finds the first entry in the path for a binary and checks to make sure it’s not us. Warn if it is us as that won’t work in 2.x



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/sugarjar/util.rb', line 286

def which_nofail(cmd)
  ENV['PATH'].split(File::PATH_SEPARATOR).each do |dir|
    p = File.join(dir, cmd)
    next unless File.exist?(p) && File.executable?(p)

    if File.basename(File.realpath(p)) == 'sj'
      SugarJar::Log.error(
        "'#{cmd}' is linked to 'sj' which is no longer supported.",
      )
      next
    end
    return p
  end
  false
end