sugarjar 2.0.1 → 2.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f79105e6ffa39ec2c1cdabd741fef41249e031ebe17d0cd5d0fbf4c3691ef4d7
4
- data.tar.gz: 4a8fbf1f68268e9e0ffcee9dcbbd74d3e906ce3bb28aa278424f5959d377593b
3
+ metadata.gz: d0f9fa156720c59e1fc95f289273950176cad61ab82a67018d836a0ee878d81e
4
+ data.tar.gz: 6bec27c224878e6faee0e5801dec631e39db36170b759b6bf335a045cdda75a0
5
5
  SHA512:
6
- metadata.gz: 3b910f580bf32c0dc88ba7c5504c2e2e14954ae599b29d4e6206aee050cea16b10c881ed44e511c3c86be7b019fc28248f844214e0c8a3f0c236a0718211b7c3
7
- data.tar.gz: 5b3d5b5bf7c12ca23dec569b7a5f9161494349412fd5eba4f1572dfd0f37cc2f6c2036a7b799b0cb5861c4b18aeb80960a059a5baa3de3bea48656a4115116b5
6
+ metadata.gz: e4a5e2900d551ce1bb4bc8f9899e37dff28d6faaa7c9a46e96c140febb3e8b240465499e491054525aed0c470e8892af2f8d3d546f8c19925c070d283df45478
7
+ data.tar.gz: 979dce6e20a27fb94a11432fa5a39912693539de948f0a6278eb6662f68a6f62846e2d94667506094588592e99d5aba7fe57843e41771a55b128699a09595dce
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # SugarJar Changelog
2
2
 
3
+ ## 2.0.2 (2026-01-08)
4
+
5
+ * Fix `branchclean` logic to properly compare with the target branch
6
+ (might have refused to clean branches that could be cleaned)
7
+ * Add new commands to handle remote branch cleanup as well as rename
8
+ `bclean` (keeping backwards compatible aliases):
9
+ * `localbranchclean` / `lbclean` - local branch clean. aliased as
10
+ `bclean` for back-comat
11
+ * `localbranchcleanall` / `lbcleanall` - local all branch clean aliased
12
+ as `bcleanall` for back-compat
13
+ * `remotebranchclean` / `rbclean` - remote branch clean
14
+ * `remotebranchcleanall` / `rbcleanall` - remote all branch clean
15
+ * `globalbranchclean` / `gbclean` - local+remote branch clean
16
+ * `globalbranchcleanall` / `gbcleanall` - local+remote all branch clean
17
+ * Added new `sync` command to aid syncing branches across multiple workstations,
18
+ see help for details.
19
+ * Fix meta-ref handling which fixes crashes when using `smartlog` during rebases
20
+ * Handle worktress gracefully when doing branch cleans
21
+ * Make unittests work properly outside of git repos
22
+
3
23
  ## 2.0.1 (2025-05-12)
4
24
 
5
25
  * Fix gemspec to include new library files
data/README.md CHANGED
@@ -30,6 +30,7 @@ Jump to what you're most interested in:
30
30
  * [Cleaning up your own history](#cleaning-up-your-own-history)
31
31
  * [Better feature branches](#better-feature-branches)
32
32
  * [Smartlog](#smartlog)
33
+ * [Sync work across workstations](#sync-work-across-workstations)
33
34
  * [Pulling in suggestions from the web](#pulling-in-suggestions-from-the-web)
34
35
  * [And more!](#and-more)
35
36
  * [Installation](#installation)
@@ -50,8 +51,10 @@ doesn't work. Git will tell you the branch isn't fully merged. You can, of
50
51
  course `git branch -D <branch>`, but that does no safety checks at all, it
51
52
  forces the deletion.
52
53
 
53
- Enter `sj bclean` - it determines if the contents of your branch has been merge
54
- and safely deletes if so.
54
+ Enter `sj lbclean` - it determines if the contents of your branch has been merge
55
+ and safely deletes if so. (Note: `lbclean` stands for "local branch clean", and
56
+ is aliased to `bclean` for both backwards-compatibility and also since it's the
57
+ most common branch-cleanup command).
55
58
 
56
59
  ![bclean screenshot](https://github.com/jaymzh/sugarjar/blob/main/images/bclean.png)
57
60
 
@@ -65,6 +68,17 @@ been merged:
65
68
 
66
69
  ![bcleanall screenshot](https://github.com/jaymzh/sugarjar/blob/main/images/bcleanall.png)
67
70
 
71
+ There is also `sj rbclean` ("remote branch clean") (and `sj rbcleanall`) for
72
+ cleanup of remote branches. *Note*: This cannot differentiate between
73
+ PR/feature branches which have been merged and long-lived release branches that
74
+ have been merged (e.g. if '2.0-release' is a branch and has no commits not in
75
+ main, it will be deleted).
76
+
77
+ There is even `sj gbclean` ("global branch clean") (and `sj gbcleanall`) which will
78
+ do both the local and remote cleaning.
79
+
80
+ *NOTE*: Remote branch cleaning is still experimental, use with caution!
81
+
68
82
  ### Smarter clones and remotes
69
83
 
70
84
  There's a pattern to every new repo we want to contribute to. First we fork,
@@ -275,6 +289,21 @@ smartlog` or `sj sl` for short.
275
289
 
276
290
  ![smartlog screenshot](https://github.com/jaymzh/sugarjar/blob/main/images/smartlog.png)
277
291
 
292
+ ### Sync work across workstations
293
+
294
+ If you work on multiple workstations, keeping your branches in-sync can be a
295
+ pain. SugarJar provides `sync` to help with this.
296
+
297
+ For example, if you do some work on feature `foo` on machine1 and push to
298
+ `origin/foo` (intending to eventually merge to `upstream/main`), then on
299
+ machine2, you pull that branch, do more work, which you also push to
300
+ `origin/foo`, then on machine1, you can do `sj sync` to pull down the changes
301
+ from `origin/foo`. If you have local changes, that are not already on
302
+ `origin/foo`, those will be rebased on top of the changes from `origin/foo`.
303
+
304
+ It's very similar to `sj up`, but instead of rebasing on top of the tracking
305
+ branch, it rebases on top of the push target branch.
306
+
278
307
  ### Pulling in suggestions from the web
279
308
 
280
309
  When someone 'suggests' a change in the GitHub WebUI, once you choose to commit
@@ -314,7 +343,7 @@ directly from there.
314
343
 
315
344
  Sugarjar will read in both a system-level config file
316
345
  (`/etc/sugarjar/config.yaml`) and a user-level config file
317
- `~/.config/sugarjar/config.yaml`, if they exist. Anything in the user config
346
+ (`~/.config/sugarjar/config.yaml`), if they exist. Anything in the user config
318
347
  will override the system config, and command-line options override both. The
319
348
  yaml file is a straight key-value pair of options without their '--'.
320
349
 
@@ -397,9 +426,9 @@ it on Windows, but I'll happily accept patches for Windows compatibility.
397
426
 
398
427
  **How do I get tab-completion?**
399
428
 
400
- If the package for your OS/distro didn't set it up manually, you should find
401
- that `sugarjar_completion.bash` is included in the package, and you can simply
402
- source that in your dotfiles, assuming you are using bash.
429
+ If the package for your OS/distro didn't set it up automatically, you should
430
+ find that `sugarjar_completion.bash` is included in the package, and you can
431
+ simply source that in your dotfiles, assuming you are using bash.
403
432
 
404
433
  **What happens now that Sapling is released?**
405
434
 
data/bin/sj CHANGED
@@ -118,15 +118,6 @@ COMMANDS:
118
118
  Same as "amend" but without changing the message. Alias for
119
119
  "git commit --amend --no-edit".
120
120
 
121
- bclean [<branch>]
122
- If safe, delete the current branch (or the specified branch).
123
- Unlike "git branch -d", bclean can handle squash-merged branches.
124
- Think of it as a smarter "git branch -d".
125
-
126
- bcleanall
127
- Walk all branches, and try to delete them if it's safe. See
128
- "bclean" for details.
129
-
130
121
  binfo
131
122
  Verbose information about the current branch.
132
123
 
@@ -157,14 +148,62 @@ COMMANDS:
157
148
  branches. Very convenient for keeping the branch behind a pull-
158
149
  request clean.
159
150
 
151
+ globalbranchclean, gbclean [<branch>] [<remote>]
152
+ WARNING: EXPERIMENTAL COMMAND.
153
+
154
+ Combination of "lbclean" and "rbclean". Cleans up
155
+ both local and remote branches safely. See those commands for
156
+ details.
157
+
158
+ globalbranchcleanall, gbcleanall [<remote>]
159
+ WARNING: EXPERIMENTAL COMMAND.
160
+
161
+ Safely clean all branches, both local and remote. See "gbclean"
162
+ for details.
163
+
160
164
  lint
161
165
  Run any linters configured in .sugarjar.yaml.
162
166
 
167
+ localbranchclean, lbclean [<branch>]
168
+ If safe, delete the current branch (or the specified branch).
169
+ Unlike "git branch -d", lbclean can handle squash-merged branches.
170
+ Think of it as a smarter "git branch -d".
171
+
172
+ Aliased to 'bclean' for backwards compatibility.
173
+
174
+ localbranchcleanall, lbcleanall
175
+ Walk all branches, and try to delete them if it's safe. See
176
+ "lbclean" for details.
177
+
178
+ Aliased to 'bcleanall' for backwards compatibility.
179
+
163
180
  pullsuggestions, ps
164
181
  Pull any suggestions *that have been committed* in the GitHub UI.
165
182
  This will show the diff and prompt for confirmation before
166
183
  merging. Note that a fast-forward merge will be used.
167
184
 
185
+ remotebranchclean, rbclean [<branch>] [<remote>]
186
+ WARNING: EXPERIMENTAL COMMAND.
187
+
188
+ Similar to lbclean, except safely cleans up remote branches.
189
+ Unlike many git commands, <remote> comes after <branch> so
190
+ that you can specify a branch and the remote defaults to 'origin'.
191
+ This means you can do "sj rclean" to clean the remote branch with
192
+ the same name as the local one. Note that you probably want
193
+ "sclean", which will do both local and remote cleaning in one
194
+ command.
195
+
196
+ WARNING: This command cannot differentiate release branches
197
+ that are fully merged but still need to be kept around for future
198
+ work. So if main contains everything that 2.0-devel and 3.0-devel
199
+ has, then those branches will be deleted. Use with caution.
200
+
201
+ remotebranchcleanall, rbcleanall [<remote>]
202
+ WARNING: EXPERIMENTAL COMMAND.
203
+
204
+ Walk all remote branches, and try to delete them if it's safe. See
205
+ "rbclean" for details.
206
+
168
207
  smartclone, sclone
169
208
  A smart wrapper to "git clone" that handles forking and managing
170
209
  remotes for you.
@@ -190,6 +229,20 @@ COMMANDS:
190
229
  subfeature, sf <feature>
191
230
  An alias for 'sj feature <feature> <current_branch>'
192
231
 
232
+ sync
233
+ Similar to `up`, except instead of rebasing on a tracked branch
234
+ (usually `upstream` remote), rebases to wherever our remote push
235
+ target is (usually `origin` remote). Useful for syncing work
236
+ across different machines.
237
+
238
+ For example, if you do some work on feature `foo` on machine1 and
239
+ push to `origin/foo` (intending to eventually merge to
240
+ `upstream/main`), then on machine2, you pull that branch, do more
241
+ work, which you also push to `origin/foo`, then on machine1, you
242
+ can do `sj sync` to pull down the changes from `origin/foo`. If
243
+ you have local changes, that are not already on `origin/foo`,
244
+ those will be rebased on top of the changes from `origin/foo`.
245
+
193
246
  unit
194
247
  Run any unitests configured in .sugarjar.yaml.
195
248
 
@@ -1,9 +1,17 @@
1
1
  class SugarJar
2
2
  class Commands
3
- def bclean(name = nil)
3
+ def lbclean(name = nil)
4
4
  assert_in_repo!
5
5
  name ||= current_branch
6
6
  name = fprefix(name)
7
+
8
+ wt_branches = worktree_branches
9
+
10
+ if wt_branches.include?(name)
11
+ SugarJar::Log.warn("#{name}: #{color('skipped', :yellow)} (worktree)")
12
+ return
13
+ end
14
+
7
15
  if clean_branch(name)
8
16
  SugarJar::Log.info("#{name}: #{color('reaped', :green)}")
9
17
  else
@@ -13,15 +21,58 @@ class SugarJar
13
21
  )
14
22
  end
15
23
  end
24
+ alias localbranchclean lbclean
25
+ # backcompat
26
+ alias bclean lbclean
16
27
 
17
- def bcleanall
28
+ def rbclean(name = nil, remote = nil)
29
+ assert_in_repo!
30
+ name ||= current_branch
31
+ name = fprefix(name)
32
+ remote ||= 'origin'
33
+
34
+ ref = "refs/remotes/#{remote}/#{name}"
35
+ if git_nofail('show-ref', '--quiet', ref).error?
36
+ SugarJar::Log.warn("Remote branch #{name} on #{remote} does not exist.")
37
+ return
38
+ end
39
+
40
+ if clean_branch(ref, :remote)
41
+ SugarJar::Log.info("#{ref}: #{color('reaped', :green)}")
42
+ else
43
+ die(
44
+ "#{color("Cannot clean #{ref}", :red)}! there are unmerged " +
45
+ "commits; use 'git push #{remote} -d #{name}' to forcefully delete " +
46
+ ' it.',
47
+ )
48
+ end
49
+ end
50
+ alias remotebranchclean rbclean
51
+
52
+ def gbclean(name = nil, remote = nil)
53
+ assert_in_repo!
54
+ name ||= current_branch
55
+ remote ||= 'origin'
56
+ lbclean(name)
57
+ rbclean(name, remote)
58
+ end
59
+ alias globalbranchclean gbclean
60
+
61
+ def lbcleanall
18
62
  assert_in_repo!
19
63
  curr = current_branch
64
+ wt_branches = worktree_branches
20
65
  all_local_branches.each do |branch|
21
66
  if MAIN_BRANCHES.include?(branch)
22
67
  SugarJar::Log.debug("Skipping #{branch}")
23
68
  next
24
69
  end
70
+ if wt_branches.include?(branch)
71
+ SugarJar::Log.info(
72
+ "#{branch}: #{color('skipped', :yellow)} (worktree)",
73
+ )
74
+ next
75
+ end
25
76
 
26
77
  if clean_branch(branch)
27
78
  SugarJar::Log.info("#{branch}: #{color('reaped', :green)}")
@@ -41,28 +92,87 @@ class SugarJar
41
92
  checkout_main_branch
42
93
  end
43
94
  end
95
+ alias localbranchcleanall lbcleanall
96
+ # backcomat
97
+ alias bcleanall lbcleanall
98
+
99
+ def rbcleanall(remote = nil)
100
+ assert_in_repo!
101
+ curr = current_branch
102
+ remote ||= 'origin'
103
+ all_remote_branches(remote).each do |branch|
104
+ if (MAIN_BRANCHES + ['HEAD']).include?(branch)
105
+ SugarJar::Log.debug("Skipping #{branch}")
106
+ next
107
+ end
108
+
109
+ ref = "refs/remotes/#{remote}/#{branch}"
110
+ if clean_branch(ref, :remote)
111
+ SugarJar::Log.info("#{ref}: #{color('reaped', :green)}")
112
+ else
113
+ SugarJar::Log.info("#{ref}: skipped")
114
+ SugarJar::Log.debug(
115
+ "There are unmerged commits; use 'git branch -D #{branch}' to " +
116
+ 'forcefully delete it)',
117
+ )
118
+ end
119
+ end
120
+
121
+ # Return to the branch we were on, or main
122
+ if all_local_branches.include?(curr)
123
+ git('checkout', curr)
124
+ else
125
+ checkout_main_branch
126
+ end
127
+ end
128
+ alias remotebranchcleanall rbcleanall
129
+
130
+ def gbcleanall(remote = nil)
131
+ assert_in_repo!
132
+ bcleanall
133
+ rcleanall(remote)
134
+ end
135
+ alias globalbranchcleanall gbcleanall
44
136
 
45
137
  private
46
138
 
47
- def clean_branch(name)
48
- die("Cannot remove #{name} branch") if MAIN_BRANCHES.include?(name)
139
+ # rubocop:disable Naming/PredicateMethod
140
+ def clean_branch(name, type = :local)
141
+ undeleteable = MAIN_BRANCHES.dup
142
+ undeleteable << 'HEAD' if type == :remote
143
+ die("Cannot remove #{name} branch") if undeleteable.include?(name)
49
144
  SugarJar::Log.debug('Fetch relevant remote...')
50
145
  fetch_upstream
51
- return false unless safe_to_clean(name)
146
+ fetch(remote_from_ref(name)) if type == :remote
147
+ return false unless safe_to_clean?(name)
52
148
 
53
149
  SugarJar::Log.debug('branch deemed safe to delete...')
54
- checkout_main_branch
55
- git('branch', '-D', name)
56
- rebase
150
+ if type == :remote
151
+ remote = remote_from_ref(name)
152
+ branch = branch_from_ref(name, :remote)
153
+ git('push', remote, '--delete', branch)
154
+ else
155
+ checkout_main_branch
156
+ git('branch', '-D', name)
157
+ rebase
158
+ end
57
159
  true
58
160
  end
161
+ # rubocop:enable Naming/PredicateMethod
59
162
 
60
- def safe_to_clean(branch)
163
+ def safe_to_clean?(branch)
61
164
  # cherry -v will output 1 line per commit on the target branch
62
165
  # prefixed by a - or + - anything with a - can be dropped, anything
63
166
  # else cannot.
167
+ SugarJar::Log.debug("Checking if branch #{branch} is safe to delete...")
168
+ if branch.start_with?('refs/remotes/')
169
+ remote = remote_from_ref(branch)
170
+ tracked = main_remote_branch(remote)
171
+ else
172
+ tracked = tracked_branch(branch)
173
+ end
64
174
  out = git(
65
- 'cherry', '-v', tracked_branch, branch
175
+ 'cherry', '-v', tracked, branch
66
176
  ).stdout.lines.reject do |line|
67
177
  line.start_with?('-')
68
178
  end
@@ -80,10 +190,10 @@ class SugarJar
80
190
  # First we need a temp branch to work on
81
191
  tmpbranch = "_sugar_jar.#{Process.pid}"
82
192
 
83
- git('checkout', '-b', tmpbranch, tracked_branch)
193
+ git('checkout', '-b', tmpbranch, tracked)
84
194
  s = git_nofail('merge', '--squash', branch)
85
195
  if s.error?
86
- cleanup_tmp_branch(tmpbranch, branch)
196
+ cleanup_tmp_branch(tmpbranch, branch, tracked)
87
197
  SugarJar::Log.debug(
88
198
  'Failed to merge changes into current main. This means we could ' +
89
199
  'not figure out if this is merged or not. Check manually and use ' +
@@ -95,7 +205,7 @@ class SugarJar
95
205
  s = git('diff', '--staged')
96
206
  out = s.stdout
97
207
  SugarJar::Log.debug("Squash-merged diff: #{out}")
98
- cleanup_tmp_branch(tmpbranch, branch)
208
+ cleanup_tmp_branch(tmpbranch, branch, tracked)
99
209
  if out.empty?
100
210
  SugarJar::Log.debug(
101
211
  'After squash-merging, this branch appears safe to delete',
@@ -109,9 +219,14 @@ class SugarJar
109
219
  end
110
220
  end
111
221
 
112
- def cleanup_tmp_branch(tmp, backto)
113
- git('reset', '--hard', tracked_branch)
222
+ def cleanup_tmp_branch(tmp, backto, tracked = nil)
223
+ tracked ||= tracked_branch
224
+ # Reset any changes on our temp branch from various merge attempts
225
+ # so we're in a state we know we can 'checkout' away from.
226
+ git('reset', '--hard', tracked)
227
+ # checkout whatever branch we were on before
114
228
  git('checkout', backto)
229
+ # delete our temp branch
115
230
  git('branch', '-D', tmp)
116
231
  end
117
232
  end
@@ -4,6 +4,8 @@ class SugarJar
4
4
  class Commands
5
5
  def lint
6
6
  assert_in_repo!
7
+
8
+ # does not use dirty_check! as we want a custom message
7
9
  if dirty?
8
10
  if @ignore_dirty
9
11
  SugarJar::Log.warn(
@@ -4,24 +4,11 @@ class SugarJar
4
4
  class Commands
5
5
  def pullsuggestions
6
6
  assert_in_repo!
7
-
8
- if dirty?
9
- if @ignore_dirty
10
- SugarJar::Log.warn(
11
- 'Your repo is dirty, but --ignore-dirty was specified, so ' +
12
- 'carrying on anyway.',
13
- )
14
- else
15
- SugarJar::Log.error(
16
- 'Your repo is dirty, so I am not going to push. Please commit ' +
17
- 'or amend first.',
18
- )
19
- exit(1)
20
- end
21
- end
7
+ dirty_check!
22
8
 
23
9
  src = "origin/#{current_branch}"
24
10
  fetch('origin')
11
+
25
12
  diff = git('diff', "..#{src}").stdout
26
13
  return unless diff && !diff.empty?
27
14
 
@@ -20,20 +20,7 @@ class SugarJar
20
20
  branch ||= current_branch
21
21
  end
22
22
 
23
- if dirty?
24
- if @ignore_dirty
25
- SugarJar::Log.warn(
26
- 'Your repo is dirty, but --ignore-dirty was specified, so ' +
27
- 'carrying on anyway.',
28
- )
29
- else
30
- SugarJar::Log.error(
31
- 'Your repo is dirty, so I am not going to push. Please commit ' +
32
- 'or amend first.',
33
- )
34
- exit(1)
35
- end
36
- end
23
+ dirty_check!
37
24
 
38
25
  unless run_prepush
39
26
  if @ignore_prerun_failure
@@ -52,6 +39,7 @@ class SugarJar
52
39
  puts git(*args).stderr
53
40
  end
54
41
 
42
+ # rubocop:disable Naming/PredicateMethod
55
43
  def run_prepush
56
44
  @repo_config['on_push']&.each do |item|
57
45
  SugarJar::Log.debug("Running on_push check type #{item}")
@@ -62,5 +50,6 @@ class SugarJar
62
50
  end
63
51
  true
64
52
  end
53
+ # rubocop:enable Naming/PredicateMethod
65
54
  end
66
55
  end
@@ -6,6 +6,7 @@ class SugarJar
6
6
  assert_in_repo!
7
7
  assert_common_main_branch!
8
8
 
9
+ # does not use `dirty_check!` because we don't allow overriding here
9
10
  if dirty?
10
11
  SugarJar::Log.warn(
11
12
  'Your repo is dirty, so I am not going to create a pull request. ' +
@@ -53,15 +53,35 @@ class SugarJar
53
53
  end
54
54
  end
55
55
 
56
+ def sync
57
+ assert_in_repo!
58
+ dirty_check!
59
+
60
+ src = "origin/#{current_branch}"
61
+ fetch('origin')
62
+ s = git_nofail('merge-base', '--is-ancestor', 'HEAD', src)
63
+ if s.error?
64
+ SugarJar::Log.debug(
65
+ "Choosing rebase sync since this isn't a direct ancestor",
66
+ )
67
+ rebase(src)
68
+ else
69
+ SugarJar::Log.debug('Choosing reset sync since this is an ancestor')
70
+ git('reset', '--hard', src)
71
+ end
72
+ SugarJar::Log.info("Synced to #{src}.")
73
+ end
74
+
56
75
  private
57
76
 
58
- def rebase
77
+ def rebase(base = nil)
78
+ skip_base_warning = !base.nil?
59
79
  SugarJar::Log.debug('Fetching upstream')
60
80
  fetch_upstream
61
81
  curr = current_branch
62
82
  # this isn't a hash, it's a named param, silly rubocop
63
83
  # rubocop:disable Style/HashSyntax
64
- base = tracked_branch(fallback: false)
84
+ base ||= tracked_branch(fallback: false)
65
85
  # rubocop:enable Style/HashSyntax
66
86
  unless base
67
87
  SugarJar::Log.info(
@@ -74,7 +94,8 @@ class SugarJar
74
94
  # If this is a subfeature based on a local branch which has since
75
95
  # been deleted, 'tracked branch' will automatically return <most_main>
76
96
  # so we don't need any special handling for that
77
- if !MAIN_BRANCHES.include?(curr) && base == "origin/#{curr}"
97
+ if !MAIN_BRANCHES.include?(curr) && base == "origin/#{curr}" &&
98
+ !skip_base_warning
78
99
  SugarJar::Log.warn(
79
100
  "This branch is tracking origin/#{curr}, which is probably your " +
80
101
  'downstream (where you push _to_) as opposed to your upstream ' +
@@ -119,6 +119,23 @@ class SugarJar
119
119
  die('sugarjar must be run from inside a git repo')
120
120
  end
121
121
 
122
+ def dirty_check!
123
+ return unless dirty?
124
+
125
+ if @ignore_dirty
126
+ SugarJar::Log.warn(
127
+ 'Your repo is dirty, but --ignore-dirty was specified, so ' +
128
+ 'carrying on anyway.',
129
+ )
130
+ else
131
+ SugarJar::Log.error(
132
+ 'Your repo is dirty, so I am refusing to continue. Please commit ' +
133
+ 'or amend first (or use --ignore-dirty to override).',
134
+ )
135
+ exit(1)
136
+ end
137
+ end
138
+
122
139
  def determine_main_branch(branches)
123
140
  branches.include?('main') ? 'main' : 'master'
124
141
  end
@@ -150,8 +167,10 @@ class SugarJar
150
167
  git(
151
168
  'branch', '--format', '%(refname)'
152
169
  ).stdout.lines.map do |line|
153
- next if line.start_with?('(HEAD detached')
154
-
170
+ if line.start_with?('(')
171
+ SugarJar::Log.debug("Skipping meta-branch: #{line.strip}")
172
+ next
173
+ end
155
174
  branch_from_ref(line.strip)
156
175
  end
157
176
  end
@@ -188,11 +207,13 @@ class SugarJar
188
207
  all_local_branches.reject { |x| x == most_main }.include?(base)
189
208
  end
190
209
 
191
- def tracked_branch(fallback: true)
192
- branch = nil
210
+ def tracked_branch(branch = nil, fallback: true)
211
+ curr = current_branch
212
+ git('checkout', branch) if branch && branch != curr
193
213
  s = git_nofail(
194
214
  'rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'
195
215
  )
216
+ git('checkout', curr) if branch && branch != curr
196
217
  if s.error?
197
218
  branch = fallback ? most_main : nil
198
219
  SugarJar::Log.debug("No specific tracked branch, using #{branch}")
@@ -307,6 +328,30 @@ class SugarJar
307
328
  exit(1)
308
329
  end
309
330
 
331
+ def worktree_branches
332
+ worktrees.values.map do |wt|
333
+ branch_from_ref(wt['branch'])
334
+ end
335
+ end
336
+
337
+ def worktrees
338
+ root = SugarJar::Util.repo_root
339
+ s = git('worktree', 'list', '--porcelain')
340
+ s.error!
341
+ worktrees = {}
342
+ # each entry is separated by a double newline
343
+ s.stdout.split("\n\n").each do |entry|
344
+ # then each key/val is split by a new line with the key and
345
+ # the value themselves split by a whitespace
346
+ tree = entry.split("\n").to_h(&:split)
347
+ # Skip the one
348
+ next if tree['worktree'] == root
349
+
350
+ worktrees[tree['worktree']] = tree
351
+ end
352
+ worktrees
353
+ end
354
+
310
355
  def branch_from_ref(ref, type = :local)
311
356
  # local branches are refs/head/XXXX
312
357
  # remote branches are refs/remotes/<remote>/XXXX
@@ -314,6 +359,12 @@ class SugarJar
314
359
  ref.split('/')[base..].join('/')
315
360
  end
316
361
 
362
+ def remote_from_ref(ref)
363
+ return nil unless ref.start_with?('refs/remotes/')
364
+
365
+ ref.split('/')[2]
366
+ end
367
+
317
368
  def git(*)
318
369
  SugarJar::Util.git(*, :color => @color)
319
370
  end
@@ -1,3 +1,3 @@
1
1
  class SugarJar
2
- VERSION = '2.0.1'.freeze
2
+ VERSION = '2.0.2'.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: 2.0.2
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-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deep_merge