sugarjar 1.1.3 → 2.0.1
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 +161 -0
- data/CONTRIBUTING.md +37 -0
- data/README.md +142 -264
- data/bin/sj +62 -108
- data/examples/sample_config.yaml +24 -0
- data/examples/sample_repoconfig.yaml +77 -0
- data/lib/sugarjar/commands/amend.rb +17 -0
- data/lib/sugarjar/commands/bclean.rb +118 -0
- data/lib/sugarjar/commands/branch.rb +42 -0
- data/lib/sugarjar/commands/checks.rb +139 -0
- data/lib/sugarjar/commands/debuginfo.rb +16 -0
- data/lib/sugarjar/commands/feature.rb +35 -0
- data/lib/sugarjar/commands/pullsuggestions.rb +48 -0
- data/lib/sugarjar/commands/push.rb +66 -0
- data/lib/sugarjar/commands/smartclone.rb +30 -0
- data/lib/sugarjar/commands/smartpullrequest.rb +101 -0
- data/lib/sugarjar/commands/up.rb +103 -0
- data/lib/sugarjar/commands.rb +94 -787
- data/lib/sugarjar/config.rb +22 -2
- data/lib/sugarjar/repoconfig.rb +2 -4
- data/lib/sugarjar/util.rb +33 -90
- data/lib/sugarjar/version.rb +1 -1
- data/sugarjar.gemspec +11 -5
- metadata +24 -5
data/bin/sj
CHANGED
@@ -13,7 +13,8 @@ SugarJar::Log.level = Logger::INFO
|
|
13
13
|
|
14
14
|
# Don't put defaults here, put them in SugarJar::Config - otherwise
|
15
15
|
# these defaults overwrite whatever is in config files.
|
16
|
-
options = {
|
16
|
+
options = {}
|
17
|
+
|
17
18
|
# If ENV['SUGARJAR_DEBUG'] is set, it overrides the config file,
|
18
19
|
# but not the command line options, so set that one here. Also
|
19
20
|
# start the logger at that level, in case we are debugging option loading
|
@@ -21,6 +22,7 @@ options = { 'color' => true }
|
|
21
22
|
if ENV['SUGARJAR_LOGLEVEL']
|
22
23
|
options['log_level'] = SugarJar::Log.level = ENV['SUGARJAR_LOGLEVEL'].to_sym
|
23
24
|
end
|
25
|
+
|
24
26
|
parser = OptionParser.new do |opts|
|
25
27
|
opts.banner = 'Usage: sj <command> [<args>] [<options>]'
|
26
28
|
|
@@ -29,24 +31,10 @@ parser = OptionParser.new do |opts|
|
|
29
31
|
opts.separator ''
|
30
32
|
opts.separator 'OPTIONS:'
|
31
33
|
|
32
|
-
opts.on('--[no-]fallthru', 'Fall-thru to git. [default: true]') do |fallthru|
|
33
|
-
options['fallthru'] = fallthru
|
34
|
-
end
|
35
|
-
|
36
34
|
opts.on('--feature-prefix', 'Prefix to use for feature branches') do |prefix|
|
37
35
|
options['feature_prefix'] = prefix
|
38
36
|
end
|
39
37
|
|
40
|
-
opts.on(
|
41
|
-
'--github-cli CLI',
|
42
|
-
%w{gh cli},
|
43
|
-
'Github CLI to use ("gh" or "hub" or "auto"). Auto (the default) will ' +
|
44
|
-
'prefer "gh" if it is available but will fall back to "hub." ' +
|
45
|
-
'[default: "auto"]',
|
46
|
-
) do |cli|
|
47
|
-
options['github_cli'] = cli
|
48
|
-
end
|
49
|
-
|
50
38
|
opts.on(
|
51
39
|
'--github-host HOST',
|
52
40
|
'The host for "hub". Note that we will set this in the local repo ' +
|
@@ -109,7 +97,7 @@ parser = OptionParser.new do |opts|
|
|
109
97
|
options['pr_autostack'] = autostack
|
110
98
|
end
|
111
99
|
|
112
|
-
opts.on('--[no-]
|
100
|
+
opts.on('--[no-]color', 'Enable color. [default: true]') do |color|
|
113
101
|
options['color'] = color
|
114
102
|
end
|
115
103
|
|
@@ -119,7 +107,7 @@ parser = OptionParser.new do |opts|
|
|
119
107
|
end
|
120
108
|
|
121
109
|
# rubocop:disable Layout/HeredocIndentation
|
122
|
-
opts.separator <<
|
110
|
+
opts.separator <<COMMANDTEXT
|
123
111
|
|
124
112
|
COMMANDS:
|
125
113
|
amend
|
@@ -145,6 +133,10 @@ COMMANDS:
|
|
145
133
|
br
|
146
134
|
Verbose branch list. An alias for "git branch -v".
|
147
135
|
|
136
|
+
debuginfo
|
137
|
+
Prints out a bunch of version and config information useful for
|
138
|
+
including in bug reports.
|
139
|
+
|
148
140
|
feature, f <branch_name>
|
149
141
|
Create a "feature" branch. It's morally equivalent to
|
150
142
|
"git checkout -b" except it defaults to creating it based on
|
@@ -209,99 +201,66 @@ COMMANDS:
|
|
209
201
|
|
210
202
|
upall
|
211
203
|
Same as "up", but for all branches.
|
212
|
-
|
213
|
-
version
|
214
|
-
Print the version of sugarjar, and then run 'hub version'
|
215
|
-
to show the hub and git versions.
|
216
|
-
|
217
|
-
Be sure to checkout Sapling (https://sapling-scm.com/)! SugarJar was written as
|
218
|
-
a stop-gap to get Sapling features before it was open-sourced, and as such
|
219
|
-
Sapling may serve your needs even better.
|
220
|
-
COMMANDS
|
204
|
+
COMMANDTEXT
|
221
205
|
|
222
206
|
# rubocop:enable Layout/HeredocIndentation
|
223
207
|
end
|
224
208
|
|
225
|
-
# we make a copy of these because we will assign back to the ARGV
|
226
|
-
# we parse later. We also need a pristine copy in case we want to
|
227
|
-
# run git as we were called.
|
228
|
-
argv_copy = ARGV.dup
|
229
|
-
|
230
|
-
# We don't have options yet, but we need an instance of SJ in order
|
231
|
-
# to list public methods. We will recreate it
|
232
|
-
sj = SugarJar::Commands.new(options.merge({ 'no_change' => true }))
|
233
209
|
extra_opts = []
|
210
|
+
argv_copy = ARGV.dup
|
234
211
|
|
235
|
-
#
|
236
|
-
#
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
#
|
245
|
-
#
|
246
|
-
#
|
247
|
-
#
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
212
|
+
# We want to allow people to pass in extra args to be passed to commands (like
|
213
|
+
# `amend`), but OptionParser doesn't easily allow this. So we loop over it,
|
214
|
+
# catching exceptions.
|
215
|
+
|
216
|
+
begin
|
217
|
+
# HOWEVER, anytime it throws an exception, for some reason, it clears
|
218
|
+
# out all of ARGV, or whatever you passed to as ARGV.
|
219
|
+
#
|
220
|
+
# This not only prevents further parsing, but also means we lose
|
221
|
+
# any non-option arguements (like the subcommand!)
|
222
|
+
#
|
223
|
+
# So we save a copy, and if we throw an exception, save the option that
|
224
|
+
# caused it, remove that option from our copy, and then re-populate argv
|
225
|
+
# with what's left.
|
226
|
+
#
|
227
|
+
# By doing this we not only get to parse all the options properly and
|
228
|
+
# save unknown ones, but non-option arguements, which OptionParser
|
229
|
+
# normally leaves in ARGV stay in ARGV.
|
230
|
+
saved_argv = argv_copy.dup
|
231
|
+
parser.parse!(argv_copy)
|
232
|
+
rescue OptionParser::InvalidOption => e
|
233
|
+
SugarJar::Log.debug("Saving unknown argument #{e.args}")
|
234
|
+
extra_opts += e.args
|
235
|
+
|
236
|
+
# e.args is an array, but it's only ever one arguement per exception
|
237
|
+
saved_argv.delete(e.args.first)
|
238
|
+
argv_copy = saved_argv.dup
|
254
239
|
SugarJar::Log.debug(
|
255
|
-
|
240
|
+
"Continuing option parsing with remaining ARGV: #{argv_copy}",
|
256
241
|
)
|
257
|
-
|
258
|
-
# git commands, but OptionParser doesn't easily allow this. So we
|
259
|
-
# loop over it, catching exceptions.
|
260
|
-
begin
|
261
|
-
# HOWEVER, anytime it throws an exception, for some reason, it clears
|
262
|
-
# out all of ARGV, or whatever you passed to as ARGV.
|
263
|
-
#
|
264
|
-
# This not only prevents further parsing, but also means we lose
|
265
|
-
# any non-option arguements (like the subcommand!)
|
266
|
-
#
|
267
|
-
# So we save a copy, and if we throw an exception, save the option that
|
268
|
-
# caused it, remove that option from our copy, and then re-populate argv
|
269
|
-
# with what's left.
|
270
|
-
#
|
271
|
-
# By doing this we not only get to parse all the options properly and
|
272
|
-
# save unknown ones, but non-option arguements, which OptionParser
|
273
|
-
# normally leaves in ARGV stay in ARGV.
|
274
|
-
saved_argv = argv_copy.dup
|
275
|
-
parser.parse!(argv_copy)
|
276
|
-
rescue OptionParser::InvalidOption => e
|
277
|
-
SugarJar::Log.debug("Saving unknown argument #{e.args}")
|
278
|
-
extra_opts += e.args
|
279
|
-
|
280
|
-
# e.args is an array, but it's only ever one arguement per exception
|
281
|
-
saved_argv.delete(e.args.first)
|
282
|
-
argv_copy = saved_argv.dup
|
283
|
-
SugarJar::Log.debug(
|
284
|
-
"Continuing option parsing with remaining ARGV: #{argv_copy}",
|
285
|
-
)
|
286
|
-
retry
|
287
|
-
end
|
242
|
+
retry
|
288
243
|
end
|
289
244
|
|
290
|
-
|
245
|
+
options = SugarJar::Config.config.merge(options)
|
246
|
+
SugarJar::Log.level = options['log_level'].to_sym if options['log_level']
|
291
247
|
|
248
|
+
subcommand = argv_copy.reject { |x| x.start_with?('-') }.first
|
292
249
|
if ARGV.empty? || !subcommand
|
293
250
|
puts parser
|
294
251
|
exit
|
295
252
|
end
|
296
253
|
|
297
|
-
|
254
|
+
SugarJar::Log.debug("Final config: #{options}")
|
298
255
|
|
299
|
-
# Recreate SJ with all of our options
|
300
|
-
SugarJar::Log.level = options['log_level'].to_sym if options['log_level']
|
301
256
|
sj = SugarJar::Commands.new(options)
|
302
|
-
|
257
|
+
valid_commands = sj.public_methods - Object.public_methods
|
303
258
|
is_valid_command = valid_commands.include?(subcommand.to_sym)
|
304
|
-
|
259
|
+
# We can't do .delete(subcommand) because someone could, for example
|
260
|
+
# have a branch called 'co' and then do 'sj co co' - which will then
|
261
|
+
# remove _all_ instances of 'co'. So find the first instance and remove
|
262
|
+
# that.
|
263
|
+
argv_copy.delete_at(argv_copy.find_index(subcommand))
|
305
264
|
SugarJar::Log.debug("subcommand is #{subcommand}")
|
306
265
|
|
307
266
|
# Extra options we got, plus any left over arguements are what we
|
@@ -309,25 +268,20 @@ SugarJar::Log.debug("subcommand is #{subcommand}")
|
|
309
268
|
extra_opts += argv_copy
|
310
269
|
SugarJar::Log.debug("extra unknown options: #{extra_opts}")
|
311
270
|
|
312
|
-
|
271
|
+
case subcommand
|
272
|
+
when 'help'
|
313
273
|
puts parser
|
314
274
|
exit
|
275
|
+
when 'debuginfo'
|
276
|
+
extra_opts = [options]
|
315
277
|
end
|
316
278
|
|
317
|
-
|
318
|
-
SugarJar::Log.
|
319
|
-
|
320
|
-
)
|
321
|
-
sj.send(subcommand.to_sym, *extra_opts)
|
322
|
-
elsif options['fallthru']
|
323
|
-
SugarJar::Log.debug("Falling thru to: hub #{ARGV.join(' ')}")
|
324
|
-
if options['github_cli'] == 'hub'
|
325
|
-
exec('hub', *ARGV)
|
326
|
-
else
|
327
|
-
# If we're using 'gh', it doesn't have 'git fall thru' support, so
|
328
|
-
# we pass thru directly to 'git'
|
329
|
-
exec('git', *ARGV)
|
330
|
-
end
|
331
|
-
else
|
332
|
-
SugarJar::Log.error("No such subcommand: #{subcommand}")
|
279
|
+
unless is_valid_command
|
280
|
+
SugarJar::Log.fatal("No such subcommand: #{subcommand}")
|
281
|
+
exit 1
|
333
282
|
end
|
283
|
+
|
284
|
+
SugarJar::Log.debug(
|
285
|
+
"running #{subcommand}; extra opts: #{extra_opts.join(', ')}",
|
286
|
+
)
|
287
|
+
sj.send(subcommand.to_sym, *extra_opts)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# This is a sample SugarJar config
|
2
|
+
#
|
3
|
+
# SugarJar will look for this config in:
|
4
|
+
#
|
5
|
+
# - /etc/sugarjar/config.yaml
|
6
|
+
# - ~/.config/sugarjar/config.yaml
|
7
|
+
#
|
8
|
+
# The latter will overwrite anything in the former.
|
9
|
+
#
|
10
|
+
|
11
|
+
# NOTE: This file does NOT document ALL options since any command-line option
|
12
|
+
# to SugarJar is a valid configuration in this file, so see `sj help` for full
|
13
|
+
# details.
|
14
|
+
|
15
|
+
# Autofill in my PRs from my commit message (default: true)
|
16
|
+
pr_autofile: true
|
17
|
+
|
18
|
+
# Auto stack PRs when subfeatures are detected (default is `nil`, which prompts,
|
19
|
+
# but use `true` or `false` to force an option without prompting)
|
20
|
+
pr_autostack: true
|
21
|
+
|
22
|
+
# Don't warn about deprecated config file options if they are in this
|
23
|
+
# list
|
24
|
+
ignore_deprecated_options: [ 'gh_cli' ]
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# This is a sample `repoconfig` for SugarJar
|
2
|
+
#
|
3
|
+
# Configs should be named `.sugarjar.yaml` and placed in the root
|
4
|
+
# of your repository.
|
5
|
+
#
|
6
|
+
|
7
|
+
# `include_from` is a meta config wich will read from an additional
|
8
|
+
# configuration file and merge anything from the file onto whatever is in the
|
9
|
+
# primary file. This is helpful to have a repo configuration that applies to
|
10
|
+
# all/most developers, but allow individual developers to add to over overwrite
|
11
|
+
# specific configurations for themselves. If the file does not exist, this
|
12
|
+
# configuration is ignored.
|
13
|
+
|
14
|
+
include_from: .sugarjar_local.yaml
|
15
|
+
|
16
|
+
# `overwrite_from` is a meta config which works much like `include_from`,
|
17
|
+
# except that if the file is found, everything else in this configuration file
|
18
|
+
# will be ignored and the configuration will be entirely read from the
|
19
|
+
# referenced file. If the file does not exist, this configuration is ignored.
|
20
|
+
|
21
|
+
overwrite_from: .sugarjar_local_overwrite.yaml
|
22
|
+
|
23
|
+
# `lint` is a list of scripts to run when `sj lint` is executed (or, if
|
24
|
+
# configured, to run on `sj spush`/`sj fpush` - see `on_push` below).
|
25
|
+
# Regardless of where `sj` is run from, these scripts will be run from the root
|
26
|
+
# of the repo. If a slash is detected in the first 'word' of the command, it
|
27
|
+
# is assumed it is a relative path and `sj` will check that the file exists.
|
28
|
+
|
29
|
+
lint:
|
30
|
+
- scripts/run_rubocop.sh
|
31
|
+
- scripts/run_mdl.sh
|
32
|
+
|
33
|
+
# `unit` is a list of scripts to run when `sj unit` is executed (or, if
|
34
|
+
# configured to run on `sj spush`/`sj fpush`- see `on_push` below). Regardless
|
35
|
+
# of where `sj` is run from, these scripts will be run from the root of the
|
36
|
+
# repo. If a slash is detected in the first 'word' of the command, it is
|
37
|
+
# assumed it is a relative path and `sj` will check that the file exists.
|
38
|
+
|
39
|
+
unit:
|
40
|
+
- bundle exec rspec
|
41
|
+
- scripts/run_tests.sh
|
42
|
+
|
43
|
+
# `lint_list_cmd` is like `lint`, except it's a command to run which will
|
44
|
+
# determine the proper lints to run and return them, one per line. This is
|
45
|
+
# useful, for example, when you want to only run lints relevant to the changed
|
46
|
+
# files.
|
47
|
+
|
48
|
+
lint_list_cmd: scripts/determine_linters.sh
|
49
|
+
|
50
|
+
# `unit_list_cmd` is like `unit`, except it's a command to run which will
|
51
|
+
# determine the proper units to run and return them, one per line. This is
|
52
|
+
# useful, for example, when you want to only run tests relevant to the changed
|
53
|
+
# files.
|
54
|
+
|
55
|
+
unit_list_cmd: scripts/determine_tests.sh
|
56
|
+
|
57
|
+
# `on_push` determines what checks should be run when pushing a repo. Valid
|
58
|
+
# options are `lint` and/or `unit` (or nothing, of course).
|
59
|
+
|
60
|
+
on_push: [lint] # or [lint, unit]
|
61
|
+
|
62
|
+
# `commit_template` points to a file to set the git `commit.template` config
|
63
|
+
# to. This is really useful for ensuring that everyone has the same
|
64
|
+
# template configured.
|
65
|
+
|
66
|
+
commit_template: .git_commit_template.txt
|
67
|
+
|
68
|
+
# `github_user` is the user to use when talking to GitHub. Overrides any such
|
69
|
+
# setting in the regular SugarJar config. Most useful when in the
|
70
|
+
# `include_from` file.
|
71
|
+
|
72
|
+
github_user: myuser
|
73
|
+
|
74
|
+
# `github_host` is the GitHub host to use when talking to GitHub (for hosted
|
75
|
+
# GHE). See `github_user`.
|
76
|
+
|
77
|
+
github_host: github.sample.com
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative '../util'
|
2
|
+
|
3
|
+
class SugarJar
|
4
|
+
class Commands
|
5
|
+
def amend(*)
|
6
|
+
assert_in_repo!
|
7
|
+
# This cannot use shellout since we need a full terminal for the editor
|
8
|
+
exit(system(SugarJar::Util.which('git'), 'commit', '--amend', *))
|
9
|
+
end
|
10
|
+
|
11
|
+
def qamend(*)
|
12
|
+
assert_in_repo!
|
13
|
+
SugarJar::Log.info(git('commit', '--amend', '--no-edit', *).stdout)
|
14
|
+
end
|
15
|
+
alias amendq qamend
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
class SugarJar
|
2
|
+
class Commands
|
3
|
+
def bclean(name = nil)
|
4
|
+
assert_in_repo!
|
5
|
+
name ||= current_branch
|
6
|
+
name = fprefix(name)
|
7
|
+
if clean_branch(name)
|
8
|
+
SugarJar::Log.info("#{name}: #{color('reaped', :green)}")
|
9
|
+
else
|
10
|
+
die(
|
11
|
+
"#{color("Cannot clean #{name}", :red)}! there are unmerged " +
|
12
|
+
"commits; use 'git branch -D #{name}' to forcefully delete it.",
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def bcleanall
|
18
|
+
assert_in_repo!
|
19
|
+
curr = current_branch
|
20
|
+
all_local_branches.each do |branch|
|
21
|
+
if MAIN_BRANCHES.include?(branch)
|
22
|
+
SugarJar::Log.debug("Skipping #{branch}")
|
23
|
+
next
|
24
|
+
end
|
25
|
+
|
26
|
+
if clean_branch(branch)
|
27
|
+
SugarJar::Log.info("#{branch}: #{color('reaped', :green)}")
|
28
|
+
else
|
29
|
+
SugarJar::Log.info("#{branch}: skipped")
|
30
|
+
SugarJar::Log.debug(
|
31
|
+
"There are unmerged commits; use 'git branch -D #{branch}' to " +
|
32
|
+
'forcefully delete it)',
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Return to the branch we were on, or main
|
38
|
+
if all_local_branches.include?(curr)
|
39
|
+
git('checkout', curr)
|
40
|
+
else
|
41
|
+
checkout_main_branch
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def clean_branch(name)
|
48
|
+
die("Cannot remove #{name} branch") if MAIN_BRANCHES.include?(name)
|
49
|
+
SugarJar::Log.debug('Fetch relevant remote...')
|
50
|
+
fetch_upstream
|
51
|
+
return false unless safe_to_clean(name)
|
52
|
+
|
53
|
+
SugarJar::Log.debug('branch deemed safe to delete...')
|
54
|
+
checkout_main_branch
|
55
|
+
git('branch', '-D', name)
|
56
|
+
rebase
|
57
|
+
true
|
58
|
+
end
|
59
|
+
|
60
|
+
def safe_to_clean(branch)
|
61
|
+
# cherry -v will output 1 line per commit on the target branch
|
62
|
+
# prefixed by a - or + - anything with a - can be dropped, anything
|
63
|
+
# else cannot.
|
64
|
+
out = git(
|
65
|
+
'cherry', '-v', tracked_branch, branch
|
66
|
+
).stdout.lines.reject do |line|
|
67
|
+
line.start_with?('-')
|
68
|
+
end
|
69
|
+
if out.empty?
|
70
|
+
SugarJar::Log.debug(
|
71
|
+
"cherry-pick shows branch #{branch} obviously safe to delete",
|
72
|
+
)
|
73
|
+
return true
|
74
|
+
end
|
75
|
+
|
76
|
+
# if the "easy" check didn't work, it's probably because there
|
77
|
+
# was a squash-merge. To check for that we make our own squash
|
78
|
+
# merge to upstream/main and see if that has any delta
|
79
|
+
|
80
|
+
# First we need a temp branch to work on
|
81
|
+
tmpbranch = "_sugar_jar.#{Process.pid}"
|
82
|
+
|
83
|
+
git('checkout', '-b', tmpbranch, tracked_branch)
|
84
|
+
s = git_nofail('merge', '--squash', branch)
|
85
|
+
if s.error?
|
86
|
+
cleanup_tmp_branch(tmpbranch, branch)
|
87
|
+
SugarJar::Log.debug(
|
88
|
+
'Failed to merge changes into current main. This means we could ' +
|
89
|
+
'not figure out if this is merged or not. Check manually and use ' +
|
90
|
+
"'git branch -D #{branch}' if it is safe to do so.",
|
91
|
+
)
|
92
|
+
return false
|
93
|
+
end
|
94
|
+
|
95
|
+
s = git('diff', '--staged')
|
96
|
+
out = s.stdout
|
97
|
+
SugarJar::Log.debug("Squash-merged diff: #{out}")
|
98
|
+
cleanup_tmp_branch(tmpbranch, branch)
|
99
|
+
if out.empty?
|
100
|
+
SugarJar::Log.debug(
|
101
|
+
'After squash-merging, this branch appears safe to delete',
|
102
|
+
)
|
103
|
+
true
|
104
|
+
else
|
105
|
+
SugarJar::Log.debug(
|
106
|
+
'After squash-merging, this branch is NOT fully merged to main',
|
107
|
+
)
|
108
|
+
false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def cleanup_tmp_branch(tmp, backto)
|
113
|
+
git('reset', '--hard', tracked_branch)
|
114
|
+
git('checkout', backto)
|
115
|
+
git('branch', '-D', tmp)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class SugarJar
|
2
|
+
class Commands
|
3
|
+
def checkout(*args)
|
4
|
+
assert_in_repo!
|
5
|
+
# Pop the last arguement, which is _probably_ a branch name
|
6
|
+
# and then add any featureprefix, and if _that_ is a branch
|
7
|
+
# name, replace the last arguement with that
|
8
|
+
name = args.last
|
9
|
+
bname = fprefix(name)
|
10
|
+
if all_local_branches.include?(bname)
|
11
|
+
SugarJar::Log.debug("Featurepefixing #{name} -> #{bname}")
|
12
|
+
args[-1] = bname
|
13
|
+
end
|
14
|
+
s = git('checkout', *args)
|
15
|
+
SugarJar::Log.info(s.stderr + s.stdout.chomp)
|
16
|
+
end
|
17
|
+
alias co checkout
|
18
|
+
|
19
|
+
def br
|
20
|
+
assert_in_repo!
|
21
|
+
SugarJar::Log.info(git('branch', '-v').stdout.chomp)
|
22
|
+
end
|
23
|
+
|
24
|
+
def binfo
|
25
|
+
assert_in_repo!
|
26
|
+
SugarJar::Log.info(git(
|
27
|
+
'log', '--graph', '--oneline', '--decorate', '--boundary',
|
28
|
+
"#{tracked_branch}.."
|
29
|
+
).stdout.chomp)
|
30
|
+
end
|
31
|
+
|
32
|
+
# binfo for all branches
|
33
|
+
def smartlog
|
34
|
+
assert_in_repo!
|
35
|
+
SugarJar::Log.info(git(
|
36
|
+
'log', '--graph', '--oneline', '--decorate', '--boundary',
|
37
|
+
'--branches', "#{most_main}.."
|
38
|
+
).stdout.chomp)
|
39
|
+
end
|
40
|
+
alias sl smartlog
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require_relative '../util'
|
2
|
+
|
3
|
+
class SugarJar
|
4
|
+
class Commands
|
5
|
+
def lint
|
6
|
+
assert_in_repo!
|
7
|
+
if dirty?
|
8
|
+
if @ignore_dirty
|
9
|
+
SugarJar::Log.warn(
|
10
|
+
'Your repo is dirty, but --ignore-dirty was specified, so ' +
|
11
|
+
'carrying on anyway. If the linter autocorrects, the displayed ' +
|
12
|
+
'diff will be misleading',
|
13
|
+
)
|
14
|
+
else
|
15
|
+
SugarJar::Log.error(
|
16
|
+
'Your repo is dirty, but --ignore-dirty was not specified. ' +
|
17
|
+
'Refusing to run lint. This is to ensure that if the linter ' +
|
18
|
+
'autocorrects, we can show the correct diff.',
|
19
|
+
)
|
20
|
+
exit(1)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
exit(1) unless run_check('lint')
|
24
|
+
end
|
25
|
+
|
26
|
+
def unit
|
27
|
+
assert_in_repo!
|
28
|
+
exit(1) unless run_check('unit')
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_checks_from_command(type)
|
32
|
+
return nil unless @repo_config["#{type}_list_cmd"]
|
33
|
+
|
34
|
+
cmd = @repo_config["#{type}_list_cmd"]
|
35
|
+
short = cmd.split.first
|
36
|
+
unless File.exist?(short)
|
37
|
+
SugarJar::Log.error(
|
38
|
+
"Configured #{type}_list_cmd #{short} does not exist!",
|
39
|
+
)
|
40
|
+
return false
|
41
|
+
end
|
42
|
+
s = Mixlib::ShellOut.new(cmd).run_command
|
43
|
+
if s.error?
|
44
|
+
SugarJar::Log.error(
|
45
|
+
"#{type}_list_cmd (#{cmd}) failed: #{s.format_for_exception}",
|
46
|
+
)
|
47
|
+
return false
|
48
|
+
end
|
49
|
+
s.stdout.split("\n")
|
50
|
+
end
|
51
|
+
|
52
|
+
# determine if we're using the _list_cmd and if so run it to get the
|
53
|
+
# checks, or just use the directly-defined check, and cache it
|
54
|
+
def get_checks(type)
|
55
|
+
return @checks[type] if @checks[type]
|
56
|
+
|
57
|
+
ret = get_checks_from_command(type)
|
58
|
+
if ret
|
59
|
+
SugarJar::Log.debug("Found #{type}s: #{ret}")
|
60
|
+
@checks[type] = ret
|
61
|
+
# if it's explicitly false, we failed to run the command
|
62
|
+
elsif ret == false
|
63
|
+
@checks[type] = false
|
64
|
+
# otherwise, we move on (basically: it's nil, there was no _list_cmd)
|
65
|
+
else
|
66
|
+
SugarJar::Log.debug("[#{type}]: using listed linters: #{ret}")
|
67
|
+
@checks[type] = @repo_config[type] || []
|
68
|
+
end
|
69
|
+
@checks[type]
|
70
|
+
end
|
71
|
+
|
72
|
+
def run_check(type)
|
73
|
+
repo_root = SugarJar::Util.repo_root
|
74
|
+
Dir.chdir repo_root do
|
75
|
+
checks = get_checks(type)
|
76
|
+
# if we failed to determine the checks, the the checks have effectively
|
77
|
+
# failed
|
78
|
+
return false unless checks
|
79
|
+
|
80
|
+
checks.each do |check|
|
81
|
+
SugarJar::Log.debug("Running #{type} #{check}")
|
82
|
+
|
83
|
+
short = check.split.first
|
84
|
+
if short.include?('/')
|
85
|
+
short = File.join(repo_root, short) unless short.start_with?('/')
|
86
|
+
unless File.exist?(short)
|
87
|
+
SugarJar::Log.error("Configured #{type} #{short} does not exist!")
|
88
|
+
end
|
89
|
+
elsif !SugarJar::Util.which_nofail(short)
|
90
|
+
SugarJar::Log.error("Configured #{type} #{short} does not exist!")
|
91
|
+
return false
|
92
|
+
end
|
93
|
+
s = Mixlib::ShellOut.new(check).run_command
|
94
|
+
|
95
|
+
# Linters auto-correct, lets handle that gracefully
|
96
|
+
if type == 'lint' && dirty?
|
97
|
+
SugarJar::Log.info(
|
98
|
+
"[#{type}] #{short}: #{color('Corrected', :yellow)}",
|
99
|
+
)
|
100
|
+
SugarJar::Log.warn(
|
101
|
+
"The linter modified the repo. Here's the diff:\n",
|
102
|
+
)
|
103
|
+
puts git('diff').stdout
|
104
|
+
loop do
|
105
|
+
$stdout.print(
|
106
|
+
"\nWould you like to\n\t[q]uit and inspect\n\t[a]mend the " +
|
107
|
+
"changes to the current commit and re-run\n > ",
|
108
|
+
)
|
109
|
+
ans = $stdin.gets.strip
|
110
|
+
case ans
|
111
|
+
when /^q/
|
112
|
+
SugarJar::Log.info('Exiting at user request.')
|
113
|
+
exit(1)
|
114
|
+
when /^a/
|
115
|
+
qamend('-a')
|
116
|
+
# break here, if we get out of this loop we 'redo', assuming
|
117
|
+
# the user chose this option
|
118
|
+
break
|
119
|
+
end
|
120
|
+
end
|
121
|
+
redo
|
122
|
+
end
|
123
|
+
|
124
|
+
if s.error?
|
125
|
+
SugarJar::Log.info(
|
126
|
+
"[#{type}] #{short} #{color('failed', :red)}, output follows " +
|
127
|
+
"(see debug for more)\n#{s.stdout}",
|
128
|
+
)
|
129
|
+
SugarJar::Log.debug(s.format_for_exception)
|
130
|
+
return false
|
131
|
+
end
|
132
|
+
SugarJar::Log.info(
|
133
|
+
"[#{type}] #{short}: #{color('OK', :green)}",
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class SugarJar
|
4
|
+
class Commands
|
5
|
+
def debuginfo(*args)
|
6
|
+
puts "sugarjar version #{SugarJar::VERSION}"
|
7
|
+
puts ghcli('version').stdout
|
8
|
+
puts git('version').stdout
|
9
|
+
|
10
|
+
puts "Config: #{JSON.pretty_generate(args[0])}"
|
11
|
+
return unless @repo_config
|
12
|
+
|
13
|
+
puts "Repo config: #{JSON.pretty_generate(@repo_config)}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|