File: sj

package info (click to toggle)
sugarjar 2.0.2-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 568 kB
  • sloc: ruby: 2,228; sh: 94; makefile: 8
file content (340 lines) | stat: -rwxr-xr-x 12,327 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
#!/usr/bin/env ruby
# SugarJar

require 'optparse'
require 'mixlib/shellout'
require_relative '../lib/sugarjar/commands'
require_relative '../lib/sugarjar/config'
require_relative '../lib/sugarjar/log'
require_relative '../lib/sugarjar/util'
require_relative '../lib/sugarjar/version'

SugarJar::Log.level = Logger::INFO

# Don't put defaults here, put them in SugarJar::Config - otherwise
# these defaults overwrite whatever is in config files.
options = {}

# If ENV['SUGARJAR_DEBUG'] is set, it overrides the config file,
# but not the command line options, so set that one here. Also
# start the logger at that level, in case we are debugging option loading
# itself
if ENV['SUGARJAR_LOGLEVEL']
  options['log_level'] = SugarJar::Log.level = ENV['SUGARJAR_LOGLEVEL'].to_sym
end

parser = OptionParser.new do |opts|
  opts.banner = 'Usage: sj <command> [<args>] [<options>]'

  opts.separator ''
  opts.separator 'Command, args, and options, can appear in any order.'
  opts.separator ''
  opts.separator 'OPTIONS:'

  opts.on('--feature-prefix', 'Prefix to use for feature branches') do |prefix|
    options['feature_prefix'] = prefix
  end

  opts.on(
    '--github-host HOST',
    'The host for "hub". Note that we will set this in the local repo ' +
    'config so there is no need to have multiple config files for multiple ' +
    'github servers. Put your default one in your config file, and simply ' +
    'specify this option the first time you clone or touch a repo and it ' +
    'will be part of that repo until changed.',
  ) do |host|
    options['github_host'] = host
  end

  opts.on('--github-user USER', 'Github username') do |user|
    options['github_user'] = user
  end

  opts.on('-h', '--help', 'Print this help message') do
    puts opts
    exit
  end

  opts.on(
    '--ignore-dirty',
    'Tell command that check for a dirty repo to carry on anyway. ' +
    '[default: false]',
  ) do
    options['ignore_dirty'] = true
  end

  opts.on(
    '--ignore-prerun-failure',
    'Ignore preprun failure on *push commands. [default: false]',
  ) do
    options['ignore_prerun_failure'] = true
  end

  opts.on(
    '--log-level LEVEL',
    'Set logging level (fatal, error, warning, info, debug, trace). This can ' +
    'also be set via the SUGARJAR_LOGLEVEL environment variable. [default: ' +
    'info]',
  ) do |level|
    options['log_level'] = level
  end

  opts.on(
    '--[no-]pr-autofill',
    'When creating a PR, auto fill the title & description from the top ' +
    'commit if we are using "gh". [default: true]',
  ) do |autofill|
    options['pr_autofill'] = autofill
  end

  opts.on(
    '--[no-]pr-autostack',
    'When creating a PR, if this is a subfeature, should we make it a ' +
    'PR on the PR for the parent feature. If not specified, we prompt ' +
    'when this happens, when true always do this, when false never do ' +
    'this. Only applicable when usiing "gh" and on branch-based PRs.',
  ) do |autostack|
    options['pr_autostack'] = autostack
  end

  opts.on('--[no-]color', 'Enable color. [default: true]') do |color|
    options['color'] = color
  end

  opts.on('--version') do
    puts SugarJar::VERSION
    exit
  end

  # rubocop:disable Layout/HeredocIndentation
  opts.separator <<COMMANDTEXT

COMMANDS:
  amend
              Amend the current commit. Alias for "git commit --amend".
              Accepts other arguments such as "-a" or files.

  amendq, qamend
              Same as "amend" but without changing the message. Alias for
              "git commit --amend --no-edit".

  binfo
              Verbose information about the current branch.

  br
              Verbose branch list. An alias for "git branch -v".

  debuginfo
              Prints out a bunch of version and config information useful for
              including in bug reports.

  feature, f <branch_name>
              Create a "feature" branch. It's morally equivalent to
              "git checkout -b" except it defaults to creating it based on
              some form of 'master' instead of your current branch. In order
              of preference it will be upstream/master, origin/master, master,
              depending upon what remotes are available.

              Note that you can specify "--feature-prefix" (or add
              "feature_prefix" to your config) to have all features created
              with a prefix. This is useful for branch-based workflows where
              developers are expected to create branches names that, for
              example, start with their username.

  forcepush, fpush
              The same as "smartpush", but uses "--force-with-lease". This is
              a "safer" way of doing force-pushes and is the recommended way
              to push after rebasing or amending. Never do this to shared
              branches. Very convenient for keeping the branch behind a pull-
              request clean.

  globalbranchclean, gbclean [<branch>] [<remote>]
              WARNING: EXPERIMENTAL COMMAND.

              Combination of "lbclean" and "rbclean". Cleans up
              both local and remote branches safely. See those commands for
              details.

  globalbranchcleanall, gbcleanall [<remote>]
              WARNING: EXPERIMENTAL COMMAND.

              Safely clean all branches, both local and remote.  See "gbclean"
              for details.

  lint
              Run any linters configured in .sugarjar.yaml.

  localbranchclean, lbclean [<branch>]
              If safe, delete the current branch (or the specified branch).
              Unlike "git branch -d", lbclean can handle squash-merged branches.
              Think of it as a smarter "git branch -d".

              Aliased to 'bclean' for backwards compatibility.

  localbranchcleanall, lbcleanall
              Walk all branches, and try to delete them if it's safe. See
              "lbclean" for details.

              Aliased to 'bcleanall' for backwards compatibility.

  pullsuggestions, ps
              Pull any suggestions *that have been committed* in the GitHub UI.
              This will show the diff and prompt for confirmation before
              merging. Note that a fast-forward merge will be used.

  remotebranchclean, rbclean [<branch>] [<remote>]
              WARNING: EXPERIMENTAL COMMAND.

              Similar to lbclean, except safely cleans up remote branches.
              Unlike many git commands, <remote> comes after <branch> so
              that you can specify a branch and the remote defaults to 'origin'.
              This means you can do "sj rclean" to clean the remote branch with
              the same name as the local one. Note that you probably want
              "sclean", which will do both local and remote cleaning in one
              command.

              WARNING: This command cannot differentiate release branches
              that are fully merged but still need to be kept around for future
              work. So if main contains everything that 2.0-devel and 3.0-devel
              has, then those branches will be deleted. Use with caution.

  remotebranchcleanall, rbcleanall [<remote>]
              WARNING: EXPERIMENTAL COMMAND.

              Walk all remote branches, and try to delete them if it's safe. See
              "rbclean" for details.

  smartclone, sclone
              A smart wrapper to "git clone" that handles forking and managing
              remotes for you.
              It will clone a git repository using hub-style short name
              ("$org/$repo"). If the org of the repository is not the same
              as your github-user then it will fork the repo for you to
              your account (if not already done) and then setup your remotes
              so that "origin" is your fork and "upstream" is the upstream.

  smartlog, sl
              Inspired by Facebook's "sl" extension to Mercurial, this command
              will show you a tree of all your local branches relative to your
              upstream.

  smartpullrequest, smartpr, spr
              A smart wrapper to "hub pull-request" that checks if your repo
              is dirty before creating the pull request.

  smartpush, spush
              A smart wrapper to "git push" that runs whatever is defined in
              "on_push" in .sugarjar.yml, and only pushes if they succeed.

  subfeature, sf <feature>
              An alias for 'sj feature <feature> <current_branch>'

  sync
              Similar to `up`, except instead of rebasing on a tracked branch
              (usually `upstream` remote), rebases to wherever our remote push
              target is (usually `origin` remote). Useful for syncing work
              across different machines.

              For example, if you do some work on feature `foo` on machine1 and
              push to `origin/foo` (intending to eventually merge to
              `upstream/main`), then on machine2, you pull that branch, do more
              work, which you also push to `origin/foo`, then on machine1, you
              can do `sj sync` to pull down the changes from `origin/foo`. If
              you have local changes, that are not already on `origin/foo`,
              those will be rebased on top of the changes from `origin/foo`.

  unit
              Run any unitests configured in .sugarjar.yaml.

  up [<branch>]
              Rebase the current branch (or specified branch) intelligently.
              In most causes this will check for a main (or master) branch on
              upstream, then origin. If a branch explicitly tracks something
              else, then that will be used, instead.

  upall
              Same as "up", but for all branches.
COMMANDTEXT

  # rubocop:enable Layout/HeredocIndentation
end

extra_opts = []
argv_copy = ARGV.dup

# We want to allow people to pass in extra args to be passed to commands (like
# `amend`), but OptionParser doesn't easily allow this. So we loop over it,
# catching exceptions.

begin
  # HOWEVER, anytime it throws an exception, for some reason, it clears
  # out all of ARGV, or whatever you passed to as ARGV.
  #
  # This not only prevents further parsing, but also means we lose
  # any non-option arguements (like the subcommand!)
  #
  # So we save a copy, and if we throw an exception, save the option that
  # caused it, remove that option from our copy, and then re-populate argv
  # with what's left.
  #
  # By doing this we not only get to parse all the options properly and
  # save unknown ones, but non-option arguements, which OptionParser
  # normally leaves in ARGV stay in ARGV.
  saved_argv = argv_copy.dup
  parser.parse!(argv_copy)
rescue OptionParser::InvalidOption => e
  SugarJar::Log.debug("Saving unknown argument #{e.args}")
  extra_opts += e.args

  # e.args is an array, but it's only ever one arguement per exception
  saved_argv.delete(e.args.first)
  argv_copy = saved_argv.dup
  SugarJar::Log.debug(
    "Continuing option parsing with remaining ARGV: #{argv_copy}",
  )
  retry
end

options = SugarJar::Config.config.merge(options)
SugarJar::Log.level = options['log_level'].to_sym if options['log_level']

subcommand = argv_copy.reject { |x| x.start_with?('-') }.first
if ARGV.empty? || !subcommand
  puts parser
  exit
end

SugarJar::Log.debug("Final config: #{options}")

sj = SugarJar::Commands.new(options)
valid_commands = sj.public_methods - Object.public_methods
is_valid_command = valid_commands.include?(subcommand.to_sym)
# We can't do .delete(subcommand) because someone could, for example
# have a branch called 'co' and then do 'sj co co' - which will then
# remove _all_ instances of 'co'. So find the first instance and remove
# that.
argv_copy.delete_at(argv_copy.find_index(subcommand))
SugarJar::Log.debug("subcommand is #{subcommand}")

# Extra options we got, plus any left over arguements are what we
# pass to Commands so they can be passed to git as necessary
extra_opts += argv_copy
SugarJar::Log.debug("extra unknown options: #{extra_opts}")

case subcommand
when 'help'
  puts parser
  exit
when 'debuginfo'
  extra_opts = [options]
end

unless is_valid_command
  SugarJar::Log.fatal("No such subcommand: #{subcommand}")
  exit 1
end

SugarJar::Log.debug(
  "running #{subcommand}; extra opts: #{extra_opts.join(', ')}",
)
sj.send(subcommand.to_sym, *extra_opts)