sugarjar 2.0.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -36,13 +36,27 @@ class SugarJar
36
36
  @checks = {}
37
37
  @main_branch = nil
38
38
  @main_remote_branches = {}
39
- @ghuser = @repo_config['github_user'] || options['github_user']
40
- @ghhost = @repo_config['github_host'] || options['github_host']
39
+ # This is CONFIGURED host, which may be null, as opposed
40
+ # to the method forge_host which will always return something
41
+ @_forge_host = @repo_config['forge_host'] || options['forge_host']
42
+ @repo_forge = @repo_config['forge_type'] || options['forge_type'] ||
43
+ _determine_forge_type
44
+
45
+ unless @repo_forge.nil?
46
+ cmd = _forge_cmd
47
+ unless SugarJar::Util.which_nofail(cmd)
48
+ die("No '#{cmd}' found, please install it'")
49
+ end
50
+ end
41
51
 
42
- die("No 'gh' found, please install 'gh'") unless gh_avail?
52
+ user_option = "#{@repo_forge}_user"
53
+ @forge_user = @repo_config[user_option] || options[user_option]
43
54
 
44
- # Tell the 'gh' cli where to talk to, if not github.com
45
- ENV['GH_HOST'] = @ghhost if @ghhost
55
+ # Tell the cli where to talk to, if not default
56
+ if @_forge_host
57
+ ENV['GH_HOST'] = @_forge_host
58
+ ENV['GL_HOST'] = @_forge_host
59
+ end
46
60
 
47
61
  return if options['no_change']
48
62
 
@@ -52,12 +66,8 @@ class SugarJar
52
66
  private
53
67
 
54
68
  def forked_repo(repo, username)
55
- repo = if repo.start_with?('http', 'git@')
56
- File.basename(repo)
57
- else
58
- "#{File.basename(repo)}.git"
59
- end
60
- "git@#{@ghhost || 'github.com'}:#{username}/#{repo}"
69
+ repo = extract_repo(repo)
70
+ "git@#{forge_host}:#{username}/#{repo}.git"
61
71
  end
62
72
 
63
73
  # gh utils will default to https, but we should always default to SSH
@@ -66,12 +76,41 @@ class SugarJar
66
76
  # if they fully-qualified it, we're good
67
77
  return repo if repo.start_with?('http', 'git@')
68
78
 
69
- # otherwise, ti's a shortname
70
- cr = "git@#{@ghhost || 'github.com'}:#{repo}.git"
79
+ # otherwise, it's a shortname
80
+ cr = "git@#{forge_host}:#{repo}.git"
71
81
  SugarJar::Log.debug("canonicalized #{repo} to #{cr}")
72
82
  cr
73
83
  end
74
84
 
85
+ def forge_host
86
+ # if one is specifically configured, use that
87
+ return @_forge_host if @_forge_host
88
+
89
+ # otherwise, if we're in a repo, use the hostname of the remote
90
+ if SugarJar::Util.in_repo?
91
+ extract_host(remote_url_map['origin'])
92
+ else
93
+ @repo_forge == 'gitlab' ? 'gitlab.com' : 'github.com'
94
+ end
95
+ end
96
+
97
+ def repo_shortname(repo)
98
+ # if it's already a shortname, return
99
+ return repo unless repo.start_with?('http', 'git@')
100
+
101
+ # otherwise, parse it
102
+ if repo.start_with?('http')
103
+ bits = repo.split('/')
104
+ elsif repo.start_with?('git@')
105
+ relevant = repo.split(':').last
106
+ bits = relevant.split('/')
107
+ end
108
+ repo = bits[-1].gsub('.git', '')
109
+ org = bits[-2]
110
+
111
+ "#{org}/#{repo}"
112
+ end
113
+
75
114
  def set_commit_template
76
115
  unless SugarJar::Util.in_repo?
77
116
  SugarJar::Log.debug('Skipping set_commit_template: not in repo')
@@ -119,8 +158,29 @@ class SugarJar
119
158
  die('sugarjar must be run from inside a git repo')
120
159
  end
121
160
 
161
+ def dirty_check!
162
+ return unless dirty?
163
+
164
+ if @ignore_dirty
165
+ SugarJar::Log.warn(
166
+ 'Your repo is dirty, but --ignore-dirty was specified, so ' +
167
+ 'carrying on anyway.',
168
+ )
169
+ else
170
+ SugarJar::Log.error(
171
+ 'Your repo is dirty, so I am refusing to continue. Please commit ' +
172
+ 'or amend first (or use --ignore-dirty to override).',
173
+ )
174
+ exit(1)
175
+ end
176
+ end
177
+
122
178
  def determine_main_branch(branches)
123
- branches.include?('main') ? 'main' : 'master'
179
+ if branches.include?('main')
180
+ 'main'
181
+ elsif branches.include?('master')
182
+ 'master'
183
+ end
124
184
  end
125
185
 
126
186
  def main_branch
@@ -150,8 +210,10 @@ class SugarJar
150
210
  git(
151
211
  'branch', '--format', '%(refname)'
152
212
  ).stdout.lines.map do |line|
153
- next if line.start_with?('(HEAD detached')
154
-
213
+ if line.start_with?('(')
214
+ SugarJar::Log.debug("Skipping meta-branch: #{line.strip}")
215
+ next
216
+ end
155
217
  branch_from_ref(line.strip)
156
218
  end
157
219
  end
@@ -188,11 +250,13 @@ class SugarJar
188
250
  all_local_branches.reject { |x| x == most_main }.include?(base)
189
251
  end
190
252
 
191
- def tracked_branch(fallback: true)
192
- branch = nil
253
+ def tracked_branch(branch = nil, fallback: true)
254
+ curr = current_branch
255
+ git('checkout', branch) if branch && branch != curr
193
256
  s = git_nofail(
194
257
  'rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'
195
258
  )
259
+ git('checkout', curr) if branch && branch != curr
196
260
  if s.error?
197
261
  branch = fallback ? most_main : nil
198
262
  SugarJar::Log.debug("No specific tracked branch, using #{branch}")
@@ -242,7 +306,7 @@ class SugarJar
242
306
 
243
307
  # Whatever org we push to, regardless of if this is a fork or not
244
308
  def push_org
245
- url = git('remote', 'get-url', 'origin').stdout.strip
309
+ url = remote_url_map['origin']
246
310
  extract_org(url)
247
311
  end
248
312
 
@@ -261,8 +325,8 @@ class SugarJar
261
325
  end
262
326
  end
263
327
 
264
- def gh_avail?
265
- !!SugarJar::Util.which_nofail('gh')
328
+ def forge_cli_avail?
329
+ !!SugarJar::Util.which_nofail(_forge_cmd)
266
330
  end
267
331
 
268
332
  def fprefix(name)
@@ -302,11 +366,47 @@ class SugarJar
302
366
  File.basename(repo, '.git')
303
367
  end
304
368
 
369
+ def extract_host(repo)
370
+ if repo.start_with?('git@')
371
+ repo.split(':').first.split('@').last
372
+ elsif repo.start_with?('http')
373
+ repo.split('/')[2]
374
+ end
375
+ end
376
+
305
377
  def die(msg)
306
378
  SugarJar::Log.fatal(msg)
307
379
  exit(1)
308
380
  end
309
381
 
382
+ def release_branches
383
+ @repo_config['release_branches'] || []
384
+ end
385
+
386
+ def worktree_branches
387
+ worktrees.values.map do |wt|
388
+ branch_from_ref(wt['branch'])
389
+ end
390
+ end
391
+
392
+ def worktrees
393
+ root = SugarJar::Util.repo_root
394
+ s = git('worktree', 'list', '--porcelain')
395
+ s.error!
396
+ worktrees = {}
397
+ # each entry is separated by a double newline
398
+ s.stdout.split("\n\n").each do |entry|
399
+ # then each key/val is split by a new line with the key and
400
+ # the value themselves split by a whitespace
401
+ tree = entry.split("\n").to_h(&:split)
402
+ # Skip the one
403
+ next if tree['worktree'] == root
404
+
405
+ worktrees[tree['worktree']] = tree
406
+ end
407
+ worktrees
408
+ end
409
+
310
410
  def branch_from_ref(ref, type = :local)
311
411
  # local branches are refs/head/XXXX
312
412
  # remote branches are refs/remotes/<remote>/XXXX
@@ -314,6 +414,12 @@ class SugarJar
314
414
  ref.split('/')[base..].join('/')
315
415
  end
316
416
 
417
+ def remote_from_ref(ref)
418
+ return nil unless ref.start_with?('refs/remotes/')
419
+
420
+ ref.split('/')[2]
421
+ end
422
+
317
423
  def git(*)
318
424
  SugarJar::Util.git(*, :color => @color)
319
425
  end
@@ -322,12 +428,36 @@ class SugarJar
322
428
  SugarJar::Util.git_nofail(*, :color => @color)
323
429
  end
324
430
 
325
- def ghcli(*)
326
- SugarJar::Util.ghcli(*)
431
+ def _determine_forge_type
432
+ return nil unless SugarJar::Util.in_repo?
433
+
434
+ if remote_url_map.values.any? do |x|
435
+ x.include?('gitlab')
436
+ end
437
+ 'gitlab'
438
+ else
439
+ 'github'
440
+ end
441
+ end
442
+
443
+ def _forge_cmd
444
+ @repo_forge == 'gitlab' ? 'glab' : 'gh'
327
445
  end
328
446
 
329
- def ghcli_nofail(*)
330
- SugarJar::Util.ghcli_nofail(*)
447
+ def forge(*)
448
+ if @repo_forge == 'gitlab'
449
+ SugarJar::Util.glcli(*)
450
+ else
451
+ SugarJar::Util.ghcli(*)
452
+ end
453
+ end
454
+
455
+ def forge_nofail(*)
456
+ if @repo_forge == 'gitlab'
457
+ SugarJar::Util.glcli_nofail(*)
458
+ else
459
+ SugarJar::Util.ghcli_nofail(*)
460
+ end
331
461
  end
332
462
  end
333
463
  end
@@ -7,6 +7,7 @@ class SugarJar
7
7
  class Config
8
8
  DEFAULTS = {
9
9
  'github_user' => ENV.fetch('USER'),
10
+ 'gitlab_user' => ENV.fetch('USER'),
10
11
  'pr_autofill' => true,
11
12
  'pr_autostack' => nil,
12
13
  'color' => true,
@@ -27,6 +28,10 @@ class SugarJar
27
28
  SugarJar::Log.debug("Loading config #{f}")
28
29
  data = YAML.safe_load_file(f)
29
30
  warn_on_deprecated_configs(data, f)
31
+ if data['github_host']
32
+ data['forge_host'] = data['github_host'] if data['forge_host'].nil?
33
+ data.delete('github_host')
34
+ end
30
35
  # an empty file is a `nil` which you can't merge
31
36
  c.merge!(YAML.safe_load_file(f)) if data
32
37
  SugarJar::Log.debug("Modified config: #{c}")
@@ -41,14 +46,37 @@ class SugarJar
41
46
 
42
47
  if ignore_deprecated_options.include?(opt)
43
48
  SugarJar::Log.debug(
44
- "Not warning about deprecated option '#{opt}' in #{fname} due to " +
45
- '"ignore_deprecated_options" in that file.',
49
+ "#{fname}: Not warning about deprecated option `#{opt}` due to " +
50
+ '`ignore_deprecated_options` in that file.',
46
51
  )
47
52
  next
48
53
  end
49
54
  SugarJar::Log.warn(
50
- "Config file #{fname} contains deprecated option #{opt}. You can " +
51
- 'suppress this warning with ignore_deprecated_options.',
55
+ "#{fname}: contains deprecated option `#{opt}`. You can " +
56
+ 'suppress this warning with `ignore_deprecated_options`.',
57
+ )
58
+ end
59
+
60
+ # github_host has special handling
61
+ return unless data['github_host']
62
+
63
+ if ignore_deprecated_options.include?('github_host')
64
+ SugarJar::Log.debug(
65
+ "#{fname}: Deprecated option `github_host` found, but not " +
66
+ 'warning due to `ignore_deprecated_options` in that file.',
67
+ )
68
+ elsif data.key?('forge_host')
69
+ SugarJar::Log.warn(
70
+ "#{fname}: Deprecated option `github_host` found. " +
71
+ 'Ignoring in favor of newer `force_host` option. You can ' +
72
+ 'suppress this warning with `ignore_deprecated_options`.',
73
+ )
74
+ else
75
+ SugarJar::Log.warn(
76
+ "#{fname}: Deprecated option `github_host` found. " +
77
+ 'Treating it as if it was `forge_host` for now. Please update ' +
78
+ 'your config file to use this new option. You can suppress ' +
79
+ 'this warning with `ignore_deprecated_options`.',
52
80
  )
53
81
  end
54
82
  end
data/lib/sugarjar/log.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'mixlib/log'
2
2
 
3
+ # rubocop:disable Style/OneClassPerFile
3
4
  module Mixlib
4
5
  module Log
5
6
  # A simple formatter so that 'info' is just like 'puts'
@@ -22,3 +23,4 @@ class SugarJar
22
23
  extend Mixlib::Log
23
24
  end
24
25
  end
26
+ # rubocop:enable Style/OneClassPerFile
data/lib/sugarjar/util.rb CHANGED
@@ -46,16 +46,36 @@ class SugarJar
46
46
  s
47
47
  end
48
48
 
49
- def self.ghcli_nofail(*args)
50
- SugarJar::Log.trace("Running: gh #{args.join(' ')}")
51
- gh = which('gh')
52
- s = Mixlib::ShellOut.new([gh] + args).run_command
53
- if s.error? && s.stderr.include?('gh auth')
49
+ def self.ghcli_nofail(*)
50
+ forge_nofail('gh', *)
51
+ end
52
+
53
+ def self.ghcli(*)
54
+ s = ghcli_nofail(*)
55
+ s.error!
56
+ s
57
+ end
58
+
59
+ def self.glcli_nofail(*)
60
+ forge_nofail('glab', *)
61
+ end
62
+
63
+ def self.glcli(*)
64
+ s = glcli_nofail(*)
65
+ s.error!
66
+ s
67
+ end
68
+
69
+ def self.forge_nofail(cli, *args)
70
+ SugarJar::Log.trace("Running: #{cli} #{args.join(' ')}")
71
+ bin = which(cli)
72
+ s = Mixlib::ShellOut.new([bin] + args).run_command
73
+ if s.error? && s.stderr.include?("#{cli} auth")
54
74
  SugarJar::Log.info(
55
- 'gh was run but no github token exists. Will run "gh auth login" ' +
56
- "to force\ngh to authenticate...",
75
+ 'glab was run but no gitlab token exists. Will run ' +
76
+ '"glab auth login" to force\ngh to authenticate...',
57
77
  )
58
- unless system(gh, 'auth', 'login', '-p', 'ssh')
78
+ unless system(bin, 'auth', 'login', '-p', 'ssh')
59
79
  SugarJar::Log.fatal(
60
80
  'That failed, I will bail out. Hub needs to get a github ' +
61
81
  'token. Try running "gh auth login" (will list info about ' +
@@ -67,12 +87,6 @@ class SugarJar
67
87
  s
68
88
  end
69
89
 
70
- def self.ghcli(*)
71
- s = ghcli_nofail(*)
72
- s.error!
73
- s
74
- end
75
-
76
90
  def self.in_repo?
77
91
  s = git_nofail('rev-parse', '--is-inside-work-tree')
78
92
  !s.error? && s.stdout.strip == 'true'
@@ -1,3 +1,3 @@
1
1
  class SugarJar
2
- VERSION = '2.0.1'.freeze
2
+ VERSION = '3.0.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sugarjar
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Phil Dibowitz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-05-12 00:00:00.000000000 Z
11
+ date: 2026-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deep_merge