sugarjar 2.0.2 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +118 -51
- data/bin/sj +34 -14
- data/examples/sample_config.yaml +6 -0
- data/examples/sample_repoconfig.yaml +19 -3
- data/lib/sugarjar/commands/bclean.rb +28 -13
- data/lib/sugarjar/commands/checks.rb +25 -8
- data/lib/sugarjar/commands/debuginfo.rb +1 -1
- data/lib/sugarjar/commands/feature.rb +25 -2
- data/lib/sugarjar/commands/push.rb +1 -1
- data/lib/sugarjar/commands/smartclone.rb +47 -3
- data/lib/sugarjar/commands/smartpullrequest.rb +26 -5
- data/lib/sugarjar/commands/up.rb +42 -10
- data/lib/sugarjar/commands.rb +100 -21
- data/lib/sugarjar/config.rb +32 -4
- data/lib/sugarjar/log.rb +2 -0
- data/lib/sugarjar/util.rb +28 -14
- data/lib/sugarjar/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '0359bbf05d9c9930fb303ddcba57fd651d00de7ce046a5640c77f596fa148531'
|
|
4
|
+
data.tar.gz: 6a505ebcdc042fb4e3b6bd6c4e55e85401787f748a1f8874b4fd18e7489600e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 88050a2cc342cac84d22ed6395f5fa36df8ff98361283408ae227611c4814b23881ae4a59eef276feb160bea04c9b84e81d6cd76d7eae0c8d8caac0e199dcdad
|
|
7
|
+
data.tar.gz: 932d4c9918a3eb50c078e6e3a7dd4728ea648cca222daa460954ec51ea7109fc374ea8a8517e9cf1b71d900ac5bb44c4a6120f0a7aaa581fee87d154a6be08f7
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# SugarJar Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.0 (2026-06-08)
|
|
4
|
+
|
|
5
|
+
* Add support for GitLab
|
|
6
|
+
* Add release-branch handling
|
|
7
|
+
* Fixes to `sync`/`fsync` to not leave repo in consistent state
|
|
8
|
+
* When in manual lint/unit, allow user to skip amending, and keep testing
|
|
9
|
+
* Fix typos in various messages
|
|
10
|
+
|
|
3
11
|
## 2.0.2 (2026-01-08)
|
|
4
12
|
|
|
5
13
|
* Fix `branchclean` logic to properly compare with the target branch
|
data/README.md
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
# SugarJar
|
|
2
2
|
|
|
3
|
-
[](https://github.com/jaymzh/sugarjar/actions?query=workflow%3ALint)
|
|
6
|
+
[](https://github.com/jaymzh/sugarjar/actions?query=workflow%3AUnittests)
|
|
9
|
+
[](https://github.com/jaymzh/sugarjar/actions?query=workflow%3A%22DCO+Check%22)
|
|
12
|
+
[](https://github.com/jaymzh/sugarjar/actions/workflows/codeql.yml)
|
|
15
|
+
|
|
16
|
+
Welcome to SugarJar - a git + github/gitlab helper. The only requirements are
|
|
17
|
+
Ruby, `git`, and either [gh](https://cli.github.com/) or
|
|
18
|
+
[glab](https://docs.gitlab.com/cli/), depending on which forge you are using.
|
|
9
19
|
|
|
10
20
|
SugarJar is inspired by [arcanist](https://github.com/phacility/arcanist), and
|
|
11
21
|
its replacement at Facebook, JellyFish. Many of the features they provide for
|
|
@@ -23,9 +33,14 @@ Jump to what you're most interested in:
|
|
|
23
33
|
* [Common Use-cases](#common-use-cases)
|
|
24
34
|
* [Auto Cleanup Squash-merged branches](#auto-cleanup-squash-merged-branches)
|
|
25
35
|
* [Smarter clones and remotes](#smarter-clones-and-remotes)
|
|
26
|
-
* [Work with stacked branches more easily](
|
|
27
|
-
|
|
28
|
-
* [
|
|
36
|
+
* [Work with stacked branches more easily](
|
|
37
|
+
#work-with-stacked-branches-more-easily)
|
|
38
|
+
* [Creating Stacked PRs with subfeatures](
|
|
39
|
+
#creating-stacked-prs-with-subfeatures)
|
|
40
|
+
* [Smart release branch handling](
|
|
41
|
+
#smart-release-branch-handling)
|
|
42
|
+
* [Have a better lint/unittest experience!](
|
|
43
|
+
#have-a-better-lintunittest-experience)
|
|
29
44
|
* [Better push defaults](#better-push-defaults)
|
|
30
45
|
* [Cleaning up your own history](#cleaning-up-your-own-history)
|
|
31
46
|
* [Better feature branches](#better-feature-branches)
|
|
@@ -56,7 +71,9 @@ and safely deletes if so. (Note: `lbclean` stands for "local branch clean", and
|
|
|
56
71
|
is aliased to `bclean` for both backwards-compatibility and also since it's the
|
|
57
72
|
most common branch-cleanup command).
|
|
58
73
|
|
|
59
|
-

|
|
60
77
|
|
|
61
78
|
Will delete a branch, if it has been merged, **even if it was squash-merged**.
|
|
62
79
|
|
|
@@ -66,7 +83,12 @@ You can pass it a branch if you'd like (it defaults to the branch you're on):
|
|
|
66
83
|
But it gets better! You can use `sj bcleanall` to remove all branches that have
|
|
67
84
|
been merged:
|
|
68
85
|
|
|
69
|
-

|
|
89
|
+
|
|
90
|
+
*NOTE*: You can add long-lived release-branches to your RepoConfig to prevent
|
|
91
|
+
cleaning; see [Smart release branch handling](️#smart-release-branch-handling).
|
|
70
92
|
|
|
71
93
|
There is also `sj rbclean` ("remote branch clean") (and `sj rbcleanall`) for
|
|
72
94
|
cleanup of remote branches. *Note*: This cannot differentiate between
|
|
@@ -74,8 +96,8 @@ PR/feature branches which have been merged and long-lived release branches that
|
|
|
74
96
|
have been merged (e.g. if '2.0-release' is a branch and has no commits not in
|
|
75
97
|
main, it will be deleted).
|
|
76
98
|
|
|
77
|
-
There is even `sj gbclean` ("global branch clean") (and `sj gbcleanall`) which
|
|
78
|
-
do both the local and remote cleaning.
|
|
99
|
+
There is even `sj gbclean` ("global branch clean") (and `sj gbcleanall`) which
|
|
100
|
+
will do both the local and remote cleaning.
|
|
79
101
|
|
|
80
102
|
*NOTE*: Remote branch cleaning is still experimental, use with caution!
|
|
81
103
|
|
|
@@ -85,7 +107,9 @@ There's a pattern to every new repo we want to contribute to. First we fork,
|
|
|
85
107
|
then we clone the fork, then we add a remote of the upstream repo. It's
|
|
86
108
|
monotonous. SugarJar does this for you:
|
|
87
109
|
|
|
88
|
-

|
|
89
113
|
|
|
90
114
|
`sj` accepts both `smartclone` and `sclone` for this command.
|
|
91
115
|
|
|
@@ -112,7 +136,9 @@ First, and foremost, is `feature` and `subfeature`. Regardless of stacking, the
|
|
|
112
136
|
way to create a new feature bracnh with sugarjar is with `sj feature` (or `sj
|
|
113
137
|
f` for short):
|
|
114
138
|
|
|
115
|
-

|
|
116
142
|
|
|
117
143
|
A "feature" in SugarJar parlance just means that the branch is always created
|
|
118
144
|
from "most main" - this is usually `upstream/main`, but SJ will figure out
|
|
@@ -123,7 +149,9 @@ to fetch that remote first to make sure you're working on the latest HEAD.
|
|
|
123
149
|
When you want to create a stacked PR, you can create `subfeature`, which, at
|
|
124
150
|
its core is just a branch created from the current branch:
|
|
125
151
|
|
|
126
|
-

|
|
127
155
|
|
|
128
156
|
If you create branches like this then sugarjar can now make several things
|
|
129
157
|
much easier:
|
|
@@ -134,31 +162,41 @@ much easier:
|
|
|
134
162
|
|
|
135
163
|
There are two commands that will show you the state of your stacked branches:
|
|
136
164
|
|
|
137
|
-
* `sj binfo` - shows the current branch and its ancestors up to your primary
|
|
165
|
+
* `sj binfo` - shows the current branch and its ancestors up to your primary
|
|
166
|
+
branch
|
|
138
167
|
* `sj smartlog` (aka `sj sl`) - shows you the whole tree.
|
|
139
168
|
|
|
140
169
|
To continue with the example above, my `smartlog` might look like:
|
|
141
170
|
|
|
142
|
-

|
|
143
174
|
|
|
144
175
|
As you can see, `mynewthing` is derived from `main`, and `dependentnewthing` is
|
|
145
176
|
derived from `mynewthing`.
|
|
146
177
|
|
|
147
178
|
Now lets make a different feature stack:
|
|
148
179
|
|
|
149
|
-

|
|
150
183
|
|
|
151
184
|
The `smartlog` will now show us this tree, and it's a bit more interesting:
|
|
152
185
|
|
|
153
|
-

|
|
154
189
|
|
|
155
190
|
Here we can see from `main`, we have two branches: one going to `mynewthing`
|
|
156
191
|
and one going to `anotherfeature`. Each of those has their own dependent branch
|
|
157
192
|
on top.
|
|
158
193
|
|
|
159
|
-
Now, what happens if I make a change to `mynewthing` (the bottom of the first
|
|
194
|
+
Now, what happens if I make a change to `mynewthing` (the bottom of the first
|
|
195
|
+
stack)?
|
|
160
196
|
|
|
161
|
-

|
|
162
200
|
|
|
163
201
|
We can see here now that `dependentnewthing`, is based off a commit that _used_
|
|
164
202
|
to be `mynewthing` (`5086ee`), but `mynewthing` has moved. Both `mynewthing`
|
|
@@ -166,7 +204,9 @@ and `dependentnewthing` are derived from `5086ee` (the old `mynewthing`), but
|
|
|
166
204
|
`dependentnewthing` isn't (yet) based on the current `mynewthing`. But SugarJar
|
|
167
205
|
will handle this all correctly when we ask it to update the branch:
|
|
168
206
|
|
|
169
|
-

|
|
170
210
|
|
|
171
211
|
Here we see that SugarJar knew that `dependentnewthing` should be rebased onto
|
|
172
212
|
`mynewthing`, and it did the right thing - from main there's still the
|
|
@@ -177,7 +217,9 @@ including all 3 commits in the right order.
|
|
|
177
217
|
Now, lets say that `mynewthing` gets merged and we use `bclean` to clean it all
|
|
178
218
|
up, what happens then?
|
|
179
219
|
|
|
180
|
-

|
|
181
223
|
|
|
182
224
|
SugarJar detects that branch is gone and thus this branch should now be based
|
|
183
225
|
on the upstream main branch!
|
|
@@ -196,6 +238,24 @@ It looks like this is a subfeature, would you like to base this PR on mynewthing
|
|
|
196
238
|
...
|
|
197
239
|
```
|
|
198
240
|
|
|
241
|
+
### Smart release branch handling
|
|
242
|
+
|
|
243
|
+
You can tell sugar what release branches exist, and it will intelligently
|
|
244
|
+
handle them. So of you specify, in your repoconfig:
|
|
245
|
+
|
|
246
|
+
```yaml
|
|
247
|
+
release_branches: ['v1-branch', 'v2-branch']
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Then:
|
|
251
|
+
|
|
252
|
+
* `sj feature v1-backport-foo v2-branch` will automatically base this
|
|
253
|
+
branch on `upstream/v2-branch` (or `origin/v2-branch` as appropriate)
|
|
254
|
+
* `sj feature v2-branch` will checkout `v2-branch` with the upstream set
|
|
255
|
+
to `upstream/v2-branch` (or `origin/v2-branch` as appropriate)
|
|
256
|
+
* `sj lbclean`/`sj lbcleanall` (of all varieties) will never reap release
|
|
257
|
+
branches
|
|
258
|
+
|
|
199
259
|
### Have a better lint/unittest experience!
|
|
200
260
|
|
|
201
261
|
Ever made a PR, only to find out later that it failed tests because of some
|
|
@@ -276,8 +336,9 @@ Created feature branch dependent-feature based on test-branch
|
|
|
276
336
|
|
|
277
337
|
Additionally you can specify a `feature_prefix` in your config which will cause
|
|
278
338
|
`feature` to create branches prefixed with your `feature_prefix` and will also
|
|
279
|
-
cause `co` to checkout branches with that prefix. This is useful when
|
|
280
|
-
use branch-based workflows and branches need to be prefixed with
|
|
339
|
+
cause `co` to checkout branches with that prefix. This is useful when
|
|
340
|
+
organizations use branch-based workflows and branches need to be prefixed with
|
|
341
|
+
e.g. `$USER/`.
|
|
281
342
|
|
|
282
343
|
For example, if your prefix was `user/`, then `sj feature foo` would create
|
|
283
344
|
`user/foo`, and `sj co foo` would switch to `user/foo`.
|
|
@@ -287,7 +348,9 @@ For example, if your prefix was `user/`, then `sj feature foo` would create
|
|
|
287
348
|
Smartlog will show you a tree diagram of your branches! Simply run `sj
|
|
288
349
|
smartlog` or `sj sl` for short.
|
|
289
350
|
|
|
290
|
-

|
|
291
354
|
|
|
292
355
|
### Sync work across workstations
|
|
293
356
|
|
|
@@ -306,7 +369,7 @@ branch, it rebases on top of the push target branch.
|
|
|
306
369
|
|
|
307
370
|
### Pulling in suggestions from the web
|
|
308
371
|
|
|
309
|
-
When someone 'suggests' a change in the
|
|
372
|
+
When someone 'suggests' a change in the GH/GL WebUI, once you choose to commit
|
|
310
373
|
them, your origin and local branches are no longer in-sync. The
|
|
311
374
|
`pullsuggestions` command will attempt to merge in any remote commits to your
|
|
312
375
|
local branch. This command will show a diff and ask for confirmation before
|
|
@@ -322,19 +385,22 @@ See `sj help` for more commands!
|
|
|
322
385
|
Sugarjar is packaged in a variety of Linux distributions - see if it's on the
|
|
323
386
|
list here, and if so, use your package manager (or `gem`) to install it:
|
|
324
387
|
|
|
325
|
-
[](https://repology.org/project/sugarjar/versions)
|
|
326
391
|
|
|
327
392
|
If you are using a Linux distribution version that is end-of-life'd, click the
|
|
328
393
|
above image, it'll take you to a page that lists unsupported distro versions
|
|
329
394
|
as well (they'll have older SugarJar, but they'll probably still have some
|
|
330
395
|
version).
|
|
331
396
|
|
|
332
|
-
Ubuntu users
|
|
333
|
-
|
|
334
|
-
|
|
397
|
+
**Ubuntu users**: You can use [this PPA](
|
|
398
|
+
https://launchpad.net/~michel-slm/+archive/ubuntu/sugarjar) to get newer
|
|
399
|
+
versions for all supported Ubuntu releases (as well as some older versions).
|
|
335
400
|
Ubuntu package maintainer.
|
|
336
401
|
|
|
337
|
-
|
|
402
|
+
**MacOS users**: We recommend using Homebrew - we keep SugarJar updated in
|
|
403
|
+
Homebrew Core.
|
|
338
404
|
|
|
339
405
|
Finally, if none of those work for you, you can clone this repo and run it
|
|
340
406
|
directly from there.
|
|
@@ -351,8 +417,8 @@ See [examples/sample_config.yaml](examples/sample_config.yaml) for an example
|
|
|
351
417
|
configuration file.
|
|
352
418
|
|
|
353
419
|
In addition, the environment variable `SUGARJAR_LOGLEVEL` can be defined to set
|
|
354
|
-
a log level. This is primarily used as a way to turn debug on earlier in order
|
|
355
|
-
troubleshoot configuration parsing.
|
|
420
|
+
a log level. This is primarily used as a way to turn debug on earlier in order
|
|
421
|
+
to troubleshoot configuration parsing.
|
|
356
422
|
|
|
357
423
|
Deprecated fields will cause a warning, but you can suppress that warning by
|
|
358
424
|
defining `ignore_deprecated_options`, for example:
|
|
@@ -379,30 +445,31 @@ commit template by dropping a file in the repo. Users must do something like:
|
|
|
379
445
|
`git config commit.template <file>`. Making each developer do this is error
|
|
380
446
|
prone, so this setting will automatically set this up for each developer.
|
|
381
447
|
|
|
382
|
-
## Enterprise GitHub
|
|
448
|
+
## Enterprise GitHub/GitLab
|
|
383
449
|
|
|
384
|
-
Like `gh`, SugarJar supports
|
|
385
|
-
features just for it.
|
|
450
|
+
Like `gh` and `glab`, SugarJar supports Enterprise versions of GitHub and
|
|
451
|
+
GitLab. In fact, we provide extra features just for it.
|
|
386
452
|
|
|
387
|
-
|
|
453
|
+
In most cases, when using `sj smartclone`, pass in `--forge-host`, and
|
|
454
|
+
that's about all you need, everything else should be handlded automagically.
|
|
455
|
+
|
|
456
|
+
However, you can set `forge_host` in your global or user config, but since most
|
|
388
457
|
users will also have a few opensource repos, you can override it in the
|
|
389
458
|
Repository Config as well.
|
|
390
459
|
|
|
391
460
|
So, for example you might have:
|
|
392
461
|
|
|
393
462
|
```yaml
|
|
394
|
-
|
|
463
|
+
forge_host: gh.sample.com
|
|
395
464
|
```
|
|
396
465
|
|
|
397
466
|
In your `~/.config/sugarjar/config.yaml`, but if the `.sugarjar.yaml` in your
|
|
398
467
|
repo has:
|
|
399
468
|
|
|
400
469
|
```yaml
|
|
401
|
-
|
|
470
|
+
forge_host: github.com
|
|
402
471
|
```
|
|
403
472
|
|
|
404
|
-
Then we will configure `gh` to talk to github.com when in that repo.
|
|
405
|
-
|
|
406
473
|
## FAQ
|
|
407
474
|
|
|
408
475
|
**Why the name SugarJar?**
|
|
@@ -411,7 +478,7 @@ It's mostly a backronym. Like jellyfish, I wanted two letters that were on home
|
|
|
411
478
|
row on different sides of the keyboard to make it easy to type. I looked at the
|
|
412
479
|
possible options that where there and not taken and tried to find one I could
|
|
413
480
|
make an appropriate name out of. Since this utility adds lots of sugar to git
|
|
414
|
-
and github, it seemed appropriate.
|
|
481
|
+
and github/gitlab, it seemed appropriate.
|
|
415
482
|
|
|
416
483
|
**I'd like to package SugarJar for my favorite distro/OS, is that OK?**
|
|
417
484
|
|
|
@@ -432,12 +499,12 @@ simply source that in your dotfiles, assuming you are using bash.
|
|
|
432
499
|
|
|
433
500
|
**What happens now that Sapling is released?**
|
|
434
501
|
|
|
435
|
-
SugarJar isn't going anywhere
|
|
436
|
-
|
|
437
|
-
|
|
502
|
+
SugarJar isn't going anywhere. This was meant to replace arc/jf, which has now
|
|
503
|
+
been open-sourced as [Sapling](https://sapling-scm.com/), so I highly recommend
|
|
504
|
+
taking a look at that!
|
|
438
505
|
|
|
439
506
|
Sapling is a great tool and solves a variety of problems SugarJar will never be
|
|
440
|
-
able to. However, it is a significant workflow change
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
developed.
|
|
507
|
+
able to. However, it is a significant workflow change that won't be appropriate
|
|
508
|
+
for all users or use-cases. Similarly there are workflows and tools that
|
|
509
|
+
Sapling breaks. Further, we support some things Sapling does not. So worry not,
|
|
510
|
+
SugarJar will continue to be maintained and developed.
|
data/bin/sj
CHANGED
|
@@ -36,20 +36,36 @@ parser = OptionParser.new do |opts|
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
opts.on(
|
|
39
|
+
'--forge-host HOST',
|
|
39
40
|
'--github-host HOST',
|
|
40
|
-
'The host
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'will be part of that repo until changed.',
|
|
41
|
+
'The host of your forge (github, gitlab, etc.). Generally only needed' +
|
|
42
|
+
' when cloning (smartclone), as we can usually figure out from within' +
|
|
43
|
+
' a cloned repo. Currently accepts --github-host for backwards' +
|
|
44
|
+
' compatibility.',
|
|
45
45
|
) do |host|
|
|
46
|
-
options['
|
|
46
|
+
options['forge_host'] = host
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
opts.on('--
|
|
49
|
+
opts.on('--forge-type TYPE', 'Forge type: github, gitlab') do |type|
|
|
50
|
+
options['forge_type'] = type
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
opts.on(
|
|
54
|
+
'--github-user USER',
|
|
55
|
+
'User for github repos, unless specified in the repoconfig.' +
|
|
56
|
+
' Defaults to your local username',
|
|
57
|
+
) do |user|
|
|
50
58
|
options['github_user'] = user
|
|
51
59
|
end
|
|
52
60
|
|
|
61
|
+
opts.on(
|
|
62
|
+
'--gitlab-user USER',
|
|
63
|
+
'User for gitlab repos, unless specified in the repoconfig.' +
|
|
64
|
+
' Defaults to your local username',
|
|
65
|
+
) do |user|
|
|
66
|
+
options['gitlab_user'] = user
|
|
67
|
+
end
|
|
68
|
+
|
|
53
69
|
opts.on('-h', '--help', 'Print this help message') do
|
|
54
70
|
puts opts
|
|
55
71
|
exit
|
|
@@ -148,6 +164,10 @@ COMMANDS:
|
|
|
148
164
|
branches. Very convenient for keeping the branch behind a pull-
|
|
149
165
|
request clean.
|
|
150
166
|
|
|
167
|
+
forcesync, fsync
|
|
168
|
+
See 'sync' below, but never tries to rebase, always does a
|
|
169
|
+
hard reset.
|
|
170
|
+
|
|
151
171
|
globalbranchclean, gbclean [<branch>] [<remote>]
|
|
152
172
|
WARNING: EXPERIMENTAL COMMAND.
|
|
153
173
|
|
|
@@ -306,6 +326,12 @@ end
|
|
|
306
326
|
|
|
307
327
|
SugarJar::Log.debug("Final config: #{options}")
|
|
308
328
|
|
|
329
|
+
# if the command is help, we don't bother to create the Commands obj
|
|
330
|
+
if subcommand == 'help'
|
|
331
|
+
puts parser
|
|
332
|
+
exit
|
|
333
|
+
end
|
|
334
|
+
|
|
309
335
|
sj = SugarJar::Commands.new(options)
|
|
310
336
|
valid_commands = sj.public_methods - Object.public_methods
|
|
311
337
|
is_valid_command = valid_commands.include?(subcommand.to_sym)
|
|
@@ -321,13 +347,7 @@ SugarJar::Log.debug("subcommand is #{subcommand}")
|
|
|
321
347
|
extra_opts += argv_copy
|
|
322
348
|
SugarJar::Log.debug("extra unknown options: #{extra_opts}")
|
|
323
349
|
|
|
324
|
-
|
|
325
|
-
when 'help'
|
|
326
|
-
puts parser
|
|
327
|
-
exit
|
|
328
|
-
when 'debuginfo'
|
|
329
|
-
extra_opts = [options]
|
|
330
|
-
end
|
|
350
|
+
extra_opts = [options] if subcommand == 'debuginfo'
|
|
331
351
|
|
|
332
352
|
unless is_valid_command
|
|
333
353
|
SugarJar::Log.fatal("No such subcommand: #{subcommand}")
|
data/examples/sample_config.yaml
CHANGED
|
@@ -22,3 +22,9 @@ pr_autostack: true
|
|
|
22
22
|
# Don't warn about deprecated config file options if they are in this
|
|
23
23
|
# list
|
|
24
24
|
ignore_deprecated_options: [ 'gh_cli' ]
|
|
25
|
+
|
|
26
|
+
# User to use when cloning new github repos
|
|
27
|
+
github_user: c00ldude
|
|
28
|
+
|
|
29
|
+
# User to use when cloning new gitlab repos
|
|
30
|
+
gitlab_user: c00ldude
|
|
@@ -20,6 +20,16 @@ include_from: .sugarjar_local.yaml
|
|
|
20
20
|
|
|
21
21
|
overwrite_from: .sugarjar_local_overwrite.yaml
|
|
22
22
|
|
|
23
|
+
# `release_branches` tells SugarJar several things:
|
|
24
|
+
# 1. These branches should not be repead when running `bclean`/`bcleanall`.
|
|
25
|
+
# 2. When a feature-branch is made from a release branch (e.g. `2.x-branch`),
|
|
26
|
+
# it will actually track the release branch's upstream
|
|
27
|
+
# (e.g. `upstream/2.x-bramch`), allowing it to work the same as as a
|
|
28
|
+
# feature made off of main.
|
|
29
|
+
release_branches:
|
|
30
|
+
- 2.x-branch
|
|
31
|
+
- 3.x-branch
|
|
32
|
+
|
|
23
33
|
# `lint` is a list of scripts to run when `sj lint` is executed (or, if
|
|
24
34
|
# configured, to run on `sj spush`/`sj fpush` - see `on_push` below).
|
|
25
35
|
# Regardless of where `sj` is run from, these scripts will be run from the root
|
|
@@ -71,7 +81,13 @@ commit_template: .git_commit_template.txt
|
|
|
71
81
|
|
|
72
82
|
github_user: myuser
|
|
73
83
|
|
|
74
|
-
# `
|
|
75
|
-
#
|
|
84
|
+
# `gitlab_user` is the user to use when talking to GitLab. Overrides any such
|
|
85
|
+
# setting in the regular SugarJar config. Most useful when in the
|
|
86
|
+
# `include_from` file.
|
|
87
|
+
|
|
88
|
+
gitlab_user: myuser
|
|
89
|
+
|
|
90
|
+
# `forge_host` is the GitHub/GitLab host to use when talking to hosted versions
|
|
91
|
+
# of these services.
|
|
76
92
|
|
|
77
|
-
|
|
93
|
+
forge_host: github.sample.com
|
|
@@ -5,10 +5,11 @@ class SugarJar
|
|
|
5
5
|
name ||= current_branch
|
|
6
6
|
name = fprefix(name)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
should_skip, why = skip_branch_info(name)
|
|
9
|
+
if should_skip
|
|
10
|
+
msg = "#{name}: #{color('skipped', :yellow)}"
|
|
11
|
+
msg << " (#{why})" if why
|
|
12
|
+
SugarJar::Log.warn(msg)
|
|
12
13
|
return
|
|
13
14
|
end
|
|
14
15
|
|
|
@@ -61,16 +62,17 @@ class SugarJar
|
|
|
61
62
|
def lbcleanall
|
|
62
63
|
assert_in_repo!
|
|
63
64
|
curr = current_branch
|
|
64
|
-
|
|
65
|
+
worktree_branches
|
|
65
66
|
all_local_branches.each do |branch|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
)
|
|
67
|
+
# skip_branch info will check for MAIN_BRANCHES, but we
|
|
68
|
+
# quietly skip them.
|
|
69
|
+
next if MAIN_BRANCHES.include?(branch)
|
|
70
|
+
|
|
71
|
+
should_skip, why = skip_branch_info(branch)
|
|
72
|
+
if should_skip
|
|
73
|
+
msg = "#{branch}: #{color('skipped', :yellow)}"
|
|
74
|
+
msg << " (#{why})" if why
|
|
75
|
+
SugarJar::Log.info(msg)
|
|
74
76
|
next
|
|
75
77
|
end
|
|
76
78
|
|
|
@@ -229,5 +231,18 @@ class SugarJar
|
|
|
229
231
|
# delete our temp branch
|
|
230
232
|
git('branch', '-D', tmp)
|
|
231
233
|
end
|
|
234
|
+
|
|
235
|
+
def skip_branch_info(name)
|
|
236
|
+
return true, 'main branch' if MAIN_BRANCHES.include?(name)
|
|
237
|
+
|
|
238
|
+
wt_branches = worktree_branches
|
|
239
|
+
rel_branches = release_branches
|
|
240
|
+
|
|
241
|
+
return true, 'worktree' if wt_branches.include?(name)
|
|
242
|
+
|
|
243
|
+
return true, 'release branch' if rel_branches.include?(name)
|
|
244
|
+
|
|
245
|
+
[false, nil]
|
|
246
|
+
end
|
|
232
247
|
end
|
|
233
248
|
end
|
|
@@ -22,12 +22,12 @@ class SugarJar
|
|
|
22
22
|
exit(1)
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
|
-
exit(1) unless run_check('lint')
|
|
25
|
+
exit(1) unless run_check('lint', false)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def unit
|
|
29
29
|
assert_in_repo!
|
|
30
|
-
exit(1) unless run_check('unit')
|
|
30
|
+
exit(1) unless run_check('unit', false)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def get_checks_from_command(type)
|
|
@@ -71,7 +71,13 @@ class SugarJar
|
|
|
71
71
|
@checks[type]
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
# autorun is true when we're running from push, and false when someone
|
|
75
|
+
# ran 'lint' or 'unit' directly
|
|
76
|
+
#
|
|
77
|
+
# In the case of a autorun, if a linter changes the code, we require
|
|
78
|
+
# either the user amend, or bail out. If it's a manual run, then we
|
|
79
|
+
# allow them to just go on.
|
|
80
|
+
def run_check(type, autorun)
|
|
75
81
|
repo_root = SugarJar::Util.repo_root
|
|
76
82
|
Dir.chdir repo_root do
|
|
77
83
|
checks = get_checks(type)
|
|
@@ -81,6 +87,7 @@ class SugarJar
|
|
|
81
87
|
|
|
82
88
|
checks.each do |check|
|
|
83
89
|
SugarJar::Log.debug("Running #{type} #{check}")
|
|
90
|
+
skip_redo = false
|
|
84
91
|
|
|
85
92
|
short = check.split.first
|
|
86
93
|
if short.include?('/')
|
|
@@ -104,10 +111,15 @@ class SugarJar
|
|
|
104
111
|
)
|
|
105
112
|
puts git('diff').stdout
|
|
106
113
|
loop do
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
options = [
|
|
115
|
+
'[q]uit and inspect',
|
|
116
|
+
'[a]mend the changes to the current commit and re-run',
|
|
117
|
+
]
|
|
118
|
+
options << '[i]gnore the changes and keep going' unless autorun
|
|
119
|
+
|
|
120
|
+
msg = "\nWould you like to\n\t" + options.join("\n\t") + "\n > "
|
|
121
|
+
|
|
122
|
+
$stdout.print(msg)
|
|
111
123
|
ans = $stdin.gets.strip
|
|
112
124
|
case ans
|
|
113
125
|
when /^q/
|
|
@@ -118,9 +130,14 @@ class SugarJar
|
|
|
118
130
|
# break here, if we get out of this loop we 'redo', assuming
|
|
119
131
|
# the user chose this option
|
|
120
132
|
break
|
|
133
|
+
when /^i/
|
|
134
|
+
unless autorun
|
|
135
|
+
skip_redo = true
|
|
136
|
+
break
|
|
137
|
+
end
|
|
121
138
|
end
|
|
122
139
|
end
|
|
123
|
-
redo
|
|
140
|
+
redo unless skip_redo
|
|
124
141
|
end
|
|
125
142
|
|
|
126
143
|
if s.error?
|
|
@@ -5,10 +5,32 @@ class SugarJar
|
|
|
5
5
|
SugarJar::Log.debug("Feature: #{name}, #{base}")
|
|
6
6
|
name = fprefix(name)
|
|
7
7
|
die("#{name} already exists!") if all_local_branches.include?(name)
|
|
8
|
+
rel_branches = release_branches
|
|
8
9
|
if base
|
|
9
|
-
|
|
10
|
-
base
|
|
10
|
+
# If the user specified a base branch (sf mything base)
|
|
11
|
+
# we check if <base> is a release branch and if so, we make
|
|
12
|
+
# this track <upstream>/<base>
|
|
13
|
+
if rel_branches.include?(base)
|
|
14
|
+
newbase = "#{upstream}/#{base}"
|
|
15
|
+
SugarJar::Log.info(
|
|
16
|
+
"Base branch #{base} is a release branch, setting it to track " +
|
|
17
|
+
newbase,
|
|
18
|
+
)
|
|
19
|
+
base = newbase
|
|
20
|
+
else
|
|
21
|
+
fbase = fprefix(base)
|
|
22
|
+
base = fbase if all_local_branches.include?(fbase)
|
|
23
|
+
end
|
|
24
|
+
elsif rel_branches.include?(name)
|
|
25
|
+
# If the user did NOT specify a base *and* this new feature is
|
|
26
|
+
# a release branch, check it out tracking the upstream release
|
|
27
|
+
# branch instead of main
|
|
28
|
+
base = "#{upstream}/#{name}"
|
|
29
|
+
SugarJar::Log.info(
|
|
30
|
+
"Feature #{name} is a release branch, setting it to track #{base}",
|
|
31
|
+
)
|
|
11
32
|
else
|
|
33
|
+
# otherwise, fallback to most-main
|
|
12
34
|
base ||= most_main
|
|
13
35
|
end
|
|
14
36
|
# If our base is a local branch, don't try to parse it for a remote name
|
|
@@ -25,6 +47,7 @@ class SugarJar
|
|
|
25
47
|
end
|
|
26
48
|
alias f feature
|
|
27
49
|
|
|
50
|
+
# alias for "feature <current_branch>'
|
|
28
51
|
def subfeature(name)
|
|
29
52
|
assert_in_repo!
|
|
30
53
|
SugarJar::Log.debug("Subfature: #{name}")
|
|
@@ -43,7 +43,7 @@ class SugarJar
|
|
|
43
43
|
def run_prepush
|
|
44
44
|
@repo_config['on_push']&.each do |item|
|
|
45
45
|
SugarJar::Log.debug("Running on_push check type #{item}")
|
|
46
|
-
unless run_check(item)
|
|
46
|
+
unless run_check(item, true)
|
|
47
47
|
SugarJar::Log.info("[prepush]: #{item} #{color('failed', :red)}.")
|
|
48
48
|
return false
|
|
49
49
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
class SugarJar
|
|
2
2
|
class Commands
|
|
3
3
|
def smartclone(repo, dir = nil, *)
|
|
4
|
-
reponame =
|
|
4
|
+
reponame = extract_repo(repo)
|
|
5
5
|
dir ||= reponame
|
|
6
6
|
org = extract_org(repo)
|
|
7
7
|
|
|
@@ -13,10 +13,15 @@ class SugarJar
|
|
|
13
13
|
#
|
|
14
14
|
# Unless the repo is in our own org and cannot be forked, then it
|
|
15
15
|
# will fail.
|
|
16
|
-
if org == @
|
|
16
|
+
if org == @forge_user
|
|
17
17
|
git('clone', canonicalize_repo(repo), dir, *)
|
|
18
18
|
else
|
|
19
|
-
|
|
19
|
+
if @repo_forge == 'gitlab'
|
|
20
|
+
_gitlab_clone(org, repo, dir, *)
|
|
21
|
+
else
|
|
22
|
+
forge('repo', 'fork', '--clone', canonicalize_repo(repo), dir, *)
|
|
23
|
+
end
|
|
24
|
+
|
|
20
25
|
# make the main branch track upstream
|
|
21
26
|
Dir.chdir dir do
|
|
22
27
|
git('branch', '-u', "upstream/#{main_branch}")
|
|
@@ -26,5 +31,44 @@ class SugarJar
|
|
|
26
31
|
SugarJar::Log.info('Remotes "origin" and "upstream" configured.')
|
|
27
32
|
end
|
|
28
33
|
alias sclone smartclone
|
|
34
|
+
|
|
35
|
+
def _gitlab_clone(_org, repo, dir, *)
|
|
36
|
+
# The gitlab CLI is much less forgiving about already-forked
|
|
37
|
+
# repos, and it has no option to clone to a differently-named
|
|
38
|
+
# directory. So we have to special case it.
|
|
39
|
+
|
|
40
|
+
# glab requires a short-name for the fork command...
|
|
41
|
+
shortname = repo_shortname(repo)
|
|
42
|
+
|
|
43
|
+
# We call fork without --clone since --clone can't clone
|
|
44
|
+
# to another directory. Also, we must specify =false, or it
|
|
45
|
+
# will prompt
|
|
46
|
+
s = forge_nofail('repo', 'fork', shortname, '--clone=false')
|
|
47
|
+
|
|
48
|
+
# It fails with:
|
|
49
|
+
# 409 {message: [Project namespace name has already been taken,
|
|
50
|
+
# Name has already been taken, Path has already been taken]}
|
|
51
|
+
#
|
|
52
|
+
# when there's already a fork... or if you happen to have a name
|
|
53
|
+
# collision. There's no way to tell, so we assume it means we've
|
|
54
|
+
# already forked.
|
|
55
|
+
if s.error?
|
|
56
|
+
if s.stderr.include?(' 409 ')
|
|
57
|
+
SugarJar::Log.debug('Forking failed, probably already forked')
|
|
58
|
+
else
|
|
59
|
+
s.error!
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Now we clone ourselves...
|
|
64
|
+
git('clone', canonicalize_repo(repo), dir, *)
|
|
65
|
+
Dir.chdir dir do
|
|
66
|
+
# and then configure remotes properly
|
|
67
|
+
git('remote', 'rename', 'origin', 'upstream')
|
|
68
|
+
|
|
69
|
+
fork_url = forked_repo(repo, @forge_user)
|
|
70
|
+
git('remote', 'add', 'origin', fork_url)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
29
73
|
end
|
|
30
74
|
end
|
|
@@ -60,10 +60,25 @@ class SugarJar
|
|
|
60
60
|
|
|
61
61
|
# <org>:<branch> is the GH API syntax for:
|
|
62
62
|
# look for a branch of name <branch>, from a fork in owner <org>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
if @repo_forge == 'github'
|
|
64
|
+
# On GitHub, the head is the org and the *BRANCH* name to use as
|
|
65
|
+
# the head branch...
|
|
66
|
+
args.unshift('--head', "#{push_org}:#{curr}")
|
|
67
|
+
else
|
|
68
|
+
# On GitLab, the head is the repo (org/repo) to use as the head
|
|
69
|
+
# _repo_, and then branch is configured seperately (with -s), but
|
|
70
|
+
# we don't need that since it defaults to the local branch name.
|
|
71
|
+
#
|
|
72
|
+
# Then we need --yes for it to not prompt us
|
|
73
|
+
args.unshift('--head', "#{push_org}/#{reponame}", '--yes')
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
bin = SugarJar::Util.which(_forge_cmd)
|
|
77
|
+
subcmd = _pr_cmd
|
|
78
|
+
SugarJar::Log.trace(
|
|
79
|
+
"Running: #{bin} #{subcmd} create #{args.join(' ')}",
|
|
80
|
+
)
|
|
81
|
+
system(bin, subcmd, 'create', *args)
|
|
67
82
|
end
|
|
68
83
|
|
|
69
84
|
alias spr smartpullrequest
|
|
@@ -71,6 +86,10 @@ class SugarJar
|
|
|
71
86
|
|
|
72
87
|
private
|
|
73
88
|
|
|
89
|
+
def _pr_cmd
|
|
90
|
+
@repo_forge == 'gitlab' ? 'mr' : 'pr'
|
|
91
|
+
end
|
|
92
|
+
|
|
74
93
|
def assert_common_main_branch!
|
|
75
94
|
upstream_branch = main_remote_branch(upstream)
|
|
76
95
|
unless main_branch == upstream_branch
|
|
@@ -87,7 +106,9 @@ class SugarJar
|
|
|
87
106
|
return if upstream_branch == 'origin'
|
|
88
107
|
|
|
89
108
|
origin_branch = main_remote_branch('origin')
|
|
90
|
-
|
|
109
|
+
# NOTE: that on GL, forks don't fork any branches by default, even
|
|
110
|
+
# a main one, so if it's 'nil', then ignore.
|
|
111
|
+
return if origin_branch.nil? || origin_branch == upstream_branch
|
|
91
112
|
|
|
92
113
|
die(
|
|
93
114
|
"The main branch of your upstream (#{upstream_branch}) and your " +
|
data/lib/sugarjar/commands/up.rb
CHANGED
|
@@ -53,21 +53,53 @@ class SugarJar
|
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
def
|
|
56
|
+
def fsync
|
|
57
|
+
sync(:force => true)
|
|
58
|
+
end
|
|
59
|
+
alias forcesync fsync
|
|
60
|
+
|
|
61
|
+
def sync(force: false)
|
|
57
62
|
assert_in_repo!
|
|
58
63
|
dirty_check!
|
|
59
64
|
|
|
60
65
|
src = "origin/#{current_branch}"
|
|
61
66
|
fetch('origin')
|
|
62
|
-
|
|
63
|
-
if
|
|
64
|
-
SugarJar::Log.debug(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
want_reset = false
|
|
68
|
+
if force
|
|
69
|
+
SugarJar::Log.debug('Forcing reset instead of rebase at user request')
|
|
70
|
+
want_reset = true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
unless force
|
|
74
|
+
s = git_nofail('merge-base', '--is-ancestor', 'HEAD', src)
|
|
75
|
+
# if this IS an ancestor, we can just force reset.
|
|
76
|
+
#
|
|
77
|
+
# otherwise, we attempt a rebase to not lose anything (unless
|
|
78
|
+
# force is set)
|
|
79
|
+
if s.error?
|
|
80
|
+
SugarJar::Log.debug(
|
|
81
|
+
"Choosing rebase sync since this isn't a direct ancestor",
|
|
82
|
+
)
|
|
83
|
+
else
|
|
84
|
+
SugarJar::Log.debug('Choosing reset sync since this is an ancestor')
|
|
85
|
+
want_reset = true
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if want_reset
|
|
70
90
|
git('reset', '--hard', src)
|
|
91
|
+
else
|
|
92
|
+
rebase(src)
|
|
93
|
+
s = git_nofail('rev-parse', '--verify', 'REBASE_HEAD')
|
|
94
|
+
unless s.error?
|
|
95
|
+
SugarJar::Log.info(
|
|
96
|
+
'Rebase required input. You may continue the rebase from' +
|
|
97
|
+
' here normally, or you may abort (`git rebase --abort`)' +
|
|
98
|
+
' and instead to `sj fsync` to skip a rebase and force' +
|
|
99
|
+
' reset to the remote branch.',
|
|
100
|
+
)
|
|
101
|
+
return
|
|
102
|
+
end
|
|
71
103
|
end
|
|
72
104
|
SugarJar::Log.info("Synced to #{src}.")
|
|
73
105
|
end
|
|
@@ -85,7 +117,7 @@ class SugarJar
|
|
|
85
117
|
# rubocop:enable Style/HashSyntax
|
|
86
118
|
unless base
|
|
87
119
|
SugarJar::Log.info(
|
|
88
|
-
'The
|
|
120
|
+
'The branch we were tracking is gone, resetting tracking to ' +
|
|
89
121
|
most_main,
|
|
90
122
|
)
|
|
91
123
|
git('branch', '-u', most_main)
|
data/lib/sugarjar/commands.rb
CHANGED
|
@@ -36,13 +36,27 @@ class SugarJar
|
|
|
36
36
|
@checks = {}
|
|
37
37
|
@main_branch = nil
|
|
38
38
|
@main_remote_branches = {}
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
52
|
+
user_option = "#{@repo_forge}_user"
|
|
53
|
+
@forge_user = @repo_config[user_option] || options[user_option]
|
|
43
54
|
|
|
44
|
-
# Tell the
|
|
45
|
-
|
|
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 =
|
|
56
|
-
|
|
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,
|
|
70
|
-
cr = "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')
|
|
@@ -137,7 +176,11 @@ class SugarJar
|
|
|
137
176
|
end
|
|
138
177
|
|
|
139
178
|
def determine_main_branch(branches)
|
|
140
|
-
branches.include?('main')
|
|
179
|
+
if branches.include?('main')
|
|
180
|
+
'main'
|
|
181
|
+
elsif branches.include?('master')
|
|
182
|
+
'master'
|
|
183
|
+
end
|
|
141
184
|
end
|
|
142
185
|
|
|
143
186
|
def main_branch
|
|
@@ -263,7 +306,7 @@ class SugarJar
|
|
|
263
306
|
|
|
264
307
|
# Whatever org we push to, regardless of if this is a fork or not
|
|
265
308
|
def push_org
|
|
266
|
-
url =
|
|
309
|
+
url = remote_url_map['origin']
|
|
267
310
|
extract_org(url)
|
|
268
311
|
end
|
|
269
312
|
|
|
@@ -282,8 +325,8 @@ class SugarJar
|
|
|
282
325
|
end
|
|
283
326
|
end
|
|
284
327
|
|
|
285
|
-
def
|
|
286
|
-
!!SugarJar::Util.which_nofail(
|
|
328
|
+
def forge_cli_avail?
|
|
329
|
+
!!SugarJar::Util.which_nofail(_forge_cmd)
|
|
287
330
|
end
|
|
288
331
|
|
|
289
332
|
def fprefix(name)
|
|
@@ -323,11 +366,23 @@ class SugarJar
|
|
|
323
366
|
File.basename(repo, '.git')
|
|
324
367
|
end
|
|
325
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
|
+
|
|
326
377
|
def die(msg)
|
|
327
378
|
SugarJar::Log.fatal(msg)
|
|
328
379
|
exit(1)
|
|
329
380
|
end
|
|
330
381
|
|
|
382
|
+
def release_branches
|
|
383
|
+
@repo_config['release_branches'] || []
|
|
384
|
+
end
|
|
385
|
+
|
|
331
386
|
def worktree_branches
|
|
332
387
|
worktrees.values.map do |wt|
|
|
333
388
|
branch_from_ref(wt['branch'])
|
|
@@ -373,12 +428,36 @@ class SugarJar
|
|
|
373
428
|
SugarJar::Util.git_nofail(*, :color => @color)
|
|
374
429
|
end
|
|
375
430
|
|
|
376
|
-
def
|
|
377
|
-
SugarJar::Util.
|
|
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'
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
def forge(*)
|
|
448
|
+
if @repo_forge == 'gitlab'
|
|
449
|
+
SugarJar::Util.glcli(*)
|
|
450
|
+
else
|
|
451
|
+
SugarJar::Util.ghcli(*)
|
|
452
|
+
end
|
|
378
453
|
end
|
|
379
454
|
|
|
380
|
-
def
|
|
381
|
-
|
|
455
|
+
def forge_nofail(*)
|
|
456
|
+
if @repo_forge == 'gitlab'
|
|
457
|
+
SugarJar::Util.glcli_nofail(*)
|
|
458
|
+
else
|
|
459
|
+
SugarJar::Util.ghcli_nofail(*)
|
|
460
|
+
end
|
|
382
461
|
end
|
|
383
462
|
end
|
|
384
463
|
end
|
data/lib/sugarjar/config.rb
CHANGED
|
@@ -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
|
|
45
|
-
'
|
|
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
|
-
"
|
|
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(*
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
'
|
|
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(
|
|
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'
|
data/lib/sugarjar/version.rb
CHANGED
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:
|
|
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: 2026-
|
|
11
|
+
date: 2026-06-08 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: deep_merge
|