sugarjar 0.0.9 → 0.0.11

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: 7b12b221b467eca9aeba30396d059870a82608120ce1739ded50b4aef55ce7ba
4
- data.tar.gz: c84bcab24de60fc61a0241f7c2940d6da6d78bc7416fa3fb92500528a447f9c8
3
+ metadata.gz: 7f4c70f80f34b048ba0d6b526e5eb20fcc51164b8ef78412a38ad5c953296898
4
+ data.tar.gz: 53958fab72a6c93d35617b63ffdb178cddcf1f97837a321533836896b0336c8b
5
5
  SHA512:
6
- metadata.gz: e041cd65eb939ea1d2153bf13a8a019e05f194fcf6ce4326c69d62a2de9e34e1d036cf759c5ea17b5b42d8e7e159fc99d65ee75470580a4ed15a854fffc7c87f
7
- data.tar.gz: 2c47c2920b6a774ffb83ef16761183ac7dbfcc808a6885335bbeeee5470a29ceff343df4e3748897ab4e8e681e7c7d8b9a5fc3c7ed4d44fafaa40dda6c08b300
6
+ metadata.gz: 5b3c546fe02babc8b186cf4f5de2d6a2716e371228af676dd5550d56b1f17db9d310d135f47a1905e77a98ed32ebd6716292bc19059a1e6e9e4733f725422900
7
+ data.tar.gz: 37df61e3b4161c2737dcf6392b5db3dcb8feef5d402868b48d6f28db9cb2254ddaf9b39d2719ea88071c4f37feb32efd9d327c105c3dcde428f495957e85913e
data/LICENSE CHANGED
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright [yyyy] [name of copyright owner]
189
+ Copyright 2020-present Phil Dibowitz
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
data/README.md CHANGED
@@ -3,10 +3,11 @@
3
3
  [![Lint](https://github.com/jaymzh/sugarjar/workflows/Lint/badge.svg)](https://github.com/jaymzh/sugarjar/actions?query=workflow%3ALint)
4
4
  [![Unittest](https://github.com/jaymzh/sugarjar/workflows/Unittests/badge.svg)](https://github.com/jaymzh/sugarjar/actions?query=workflow%3AUnittests)
5
5
  [![DCO](https://github.com/jaymzh/sugarjar/workflows/DCO%20Check/badge.svg)](https://github.com/jaymzh/sugarjar/actions?query=workflow%3A%22DCO+Check%22)
6
- [![Gem Version](https://badge.fury.io/rb/sugarjar.svg)](https://badge.fury.io/rb/sugarjar)
7
6
 
8
- Welcome to SugarJar - a git/github helper. It leverages the amazing GitHub cli,
9
- [hub](https://hub.github.com/), so you'll need that installed.
7
+ Welcome to SugarJar - a git/github helper. It needs one of the GitHub CLI's:
8
+ the current default is [hub](https://hub.github.com/), but there is
9
+ experimental support for [gh](https://cli.github.com/). So you will need one of
10
+ those two installed.
10
11
 
11
12
  SugarJar is inspired by [arcanist](https://github.com/phacility/arcanist), and
12
13
  its replacement at Facebook, JellyFish. Many of the features they provide for
@@ -19,6 +20,24 @@ If you miss Mondrian or Phabricator - this is the tool for you!
19
20
 
20
21
  If you don't, there's a ton of useful stuff for everyone!
21
22
 
23
+ ## Installation
24
+
25
+ Sugarjar is packaged in a variety of Linux distributions - see if it's on the
26
+ list here, and if so, use your package manager (or `gem`) to install it:
27
+
28
+ [![Packaging status](https://repology.org/badge/vertical-allrepos/sugarjar.svg)](https://repology.org/project/sugarjar/versions)
29
+
30
+ Another option is to use our [omnibus
31
+ packages](https://github.com/jaymzh/sugarjar/releases). We keep Omnibus
32
+ packages for most distros that do not package Sugarjar. Omnibus packages are
33
+ distro packages (deb, rpm, etc.) that have all dependencies bundled up together
34
+ (including Ruby), and install in `/opt/sugarjar` allowing you to manage the
35
+ installation with your package manager, but with as a hermetically sealed app
36
+ that doesn't depend on the rest of your distro.
37
+
38
+ Finally, if none of those work for you, you can clone this repo and run it
39
+ directly from there.
40
+
22
41
  ## Auto cleanup squash-merged branches
23
42
 
24
43
  It is common for a PR to go back and forth with a variety of nits, lint fixes,
@@ -73,7 +92,7 @@ This will:
73
92
  * Clone your fork
74
93
  * Add the original as an 'upstream' remote
75
94
 
76
- Note that it takes `hub`s short-names for repos. No need to specify a full URL,
95
+ Note that it takes short names for repos. No need to specify a full URL,
77
96
  just a $org/$repo.
78
97
 
79
98
  Like `git clone`, `sj sclone` will accept an additional argument as the
@@ -198,6 +217,15 @@ smartlog` or `sj sl` for short.
198
217
 
199
218
  ![smartlog screenshot](https://github.com/jaymzh/sugarjar/blob/master/smartlog.png)
200
219
 
220
+ ## Pulling in suggestions from the web
221
+
222
+ When someone 'suggests' a change in the GitHub WebUI, once you choose to commit
223
+ them, your origin and local branches are no longer in-sync. The
224
+ `pullsuggestions` command will attempt to merge in any remote commits to your
225
+ local branch. This command will show a diff and ask for confirmation before
226
+ attempting the merge and - if allowed to continue - will use a fast-forward
227
+ merge.
228
+
201
229
  ## And more!
202
230
 
203
231
  See `sj help` for more commands!
@@ -205,8 +233,11 @@ See `sj help` for more commands!
205
233
  ## Using SugarJar as a git wrapper
206
234
 
207
235
  SugarJar, by default, will pass any command it doesn't know straight to `hub`
208
- (which passes commands **it** doesn't know to `git`). As such you can alias it
209
- to `git` and just have a super-git.
236
+ (which passes commands **it** doesn't know to `git`). If you have configured
237
+ SugarJar to use `gh` instead of `hub`, then it will pass commands straight to
238
+ `git` since `gh` doesn't act as a `git` wrapper.
239
+
240
+ As such you can alias it to `git` and just have a super-git.
210
241
 
211
242
  ```shell
212
243
  $ alias git=sj
@@ -244,8 +275,8 @@ yaml file is a straight key-value pair of options without their '--'. For
244
275
  example:
245
276
 
246
277
  ```yaml
247
- debug: true
248
- github-user: jaymzh
278
+ log_level: debug
279
+ github_user: jaymzh
249
280
  ```
250
281
 
251
282
  In addition, the environment variable `SUGARJAR_DEBUG` can be defined to set
@@ -258,9 +289,15 @@ Sugarjar looks for a `.sugarjar.yaml` in the root of the repository to tell it
258
289
  how to handle repo-specific things. Currently there options are:
259
290
 
260
291
  * `lint` - A list of scripts to run on `sj lint`. These should be linters like
261
- rubocop or pyflake.
292
+ rubocop or pyflake. Linters will be run from the root of the repo.
293
+ * `lint_list_cmd` - A command to run which will print out linters to run, one
294
+ per line. Takes precedence over `lint`. The command (and the resulting
295
+ linters) will be run from the root of the repo.
262
296
  * `unit` - A list of scripts to run on `sj unit`. These should be unittest
263
- runners like rspec or pyunit.
297
+ runners like rspec or pyunit. Test will be run fro mthe root of the repo.
298
+ * `unit_list_cmd` - A command to run which will print out the unit tests to
299
+ run, one more line. Takes precedence over `unit`. The command (and the
300
+ resulting unit tests) will be run from the root of the repo.
264
301
  * `on_push` - A list of types (`lint`, `unit`) of checks to run before pushing.
265
302
  It is highly recommended this is only `lint`. The goal here is to allow for
266
303
  the user to get quick stylistic feedback before pushing their branch to avoid
@@ -312,6 +349,16 @@ sj clone jaymzh/sugarjar --github-host githuh.com
312
349
  We will add the `hub.host` to the `sugarjar` clone so that future `hub` or `sj`
313
350
  commands work without needing to specify..
314
351
 
352
+ ## Support for `gh`
353
+
354
+ As of version 0.11 there is experimental support for the `gh` CLI (instead of
355
+ `hub`). If you would like to use `gh`, install it and then add the following
356
+ to your configuration:
357
+
358
+ ```yaml
359
+ github_cli: gh
360
+ ```
361
+
315
362
  ## FAQ
316
363
 
317
364
  Why the name SugarJar?
@@ -324,6 +371,19 @@ and github, it seemed appropriate.
324
371
 
325
372
  Why did you use `hub` instead of the newer `gh` CLI?
326
373
 
327
- `gh` is still new and not yet as feature rich as `hub`. Also I wanted SugarJar
328
- to be able to be a git wrapper, and so wrapping `hub` allows us to do that but
329
- wrapping `gh` does not.
374
+ When I originally wrote SugarJar, `gh` was in early development, and `hub` had
375
+ many more features. Now that `gh` has matured, we have experimental support for
376
+ `gh` and will switch it to the default at some point.
377
+
378
+ In addition, I wanted SugarJar to be able to be a git wrapper, and so wrapping
379
+ `hub` allows us to do that but wrapping `gh` does not.
380
+
381
+ I'd like to package SugarJar for my favorite distro/OS, is that OK?
382
+
383
+ Of course! But I'd appreciate you emailing me to give me a heads up. Doing so
384
+ will allow me to make sure it shows up in the Repology badge above as well as
385
+ stop building Omnibus packages for whatever distro.
386
+
387
+ Does it work on Windows/Mac?
388
+
389
+ It should! Though I will admit I don't regularly test on non-Linux OSes.
data/bin/sj CHANGED
@@ -33,8 +33,13 @@ parser = OptionParser.new do |opts|
33
33
  options['fallthru'] = fallthru
34
34
  end
35
35
 
36
- opts.on('--github-user USER', 'Github username') do |user|
37
- options['github_user'] = user
36
+ opts.on(
37
+ '--github-cli CLI',
38
+ %w{gh cli},
39
+ 'Github CLI to use ("gh" or "hub"). Note that support for "gh" is ' +
40
+ 'currently experimental. default: "hub"',
41
+ ) do |cli|
42
+ options['github_cli'] = cli
38
43
  end
39
44
 
40
45
  opts.on(
@@ -48,6 +53,10 @@ parser = OptionParser.new do |opts|
48
53
  options['github_host'] = host
49
54
  end
50
55
 
56
+ opts.on('--github-user USER', 'Github username') do |user|
57
+ options['github_user'] = user
58
+ end
59
+
51
60
  opts.on('-h', '--help', 'Print this help message') do
52
61
  puts opts
53
62
  exit
@@ -128,6 +137,11 @@ COMMANDS:
128
137
  lint
129
138
  Run any linters configured in .sugarjar.yaml.
130
139
 
140
+ pullsuggestions, ps
141
+ Pull any suggestions *that have been committed* in the GitHub UI.
142
+ This will show the diff and prompt for confirmation before
143
+ merging. Note that a fast-forward merge will be used.
144
+
131
145
  smartclone, sclone
132
146
  A smart wrapper to "git clone" that handles forking and managing
133
147
  remotes for you.
@@ -182,19 +196,23 @@ extra_opts = []
182
196
  config = SugarJar::Config.config
183
197
 
184
198
  valid_commands = sj.public_methods - Object.public_methods
185
-
186
- is_valid_command = ARGV.any? { |arg| valid_commands.include?(arg.to_s.to_sym) }
199
+ possible_valid_command = ARGV.any? do |arg|
200
+ valid_commands.include?(arg.to_s.to_sym)
201
+ end
187
202
 
188
203
  # if we're configured to fall thru and the subcommand isn't one
189
204
  # we recognize, don't parse the options as they may be different
190
205
  # than git's. For example `git config -l` - we error because we
191
206
  # require an arguement to `-l`.
192
- if config['fallthru'] && !is_valid_command
207
+ if config['fallthru'] && !possible_valid_command
193
208
  SugarJar::Log.debug(
194
209
  'Skipping option parsing: fall-thru is set and we do not recognize ' +
195
210
  'any subcommands',
196
211
  )
197
212
  else
213
+ SugarJar::Log.debug(
214
+ 'We MIGHT have a valid command... parse-command line options',
215
+ )
198
216
  # We want to allow people to pass in extra args to be passed to
199
217
  # git commands, but OptionParser doesn't easily allow this. So we
200
218
  # loop over it, catching exceptions.
@@ -240,6 +258,7 @@ SugarJar::Log.level = options['log_level'].to_sym if options['log_level']
240
258
  sj = SugarJar::Commands.new(options)
241
259
 
242
260
  subcommand = argv_copy.reject { |x| x.start_with?('-') }.first
261
+ is_valid_command = valid_commands.include?(subcommand.to_sym)
243
262
  argv_copy.delete(subcommand)
244
263
  SugarJar::Log.debug("subcommand is #{subcommand}")
245
264
 
@@ -260,7 +279,13 @@ if is_valid_command
260
279
  sj.send(subcommand.to_sym, *extra_opts)
261
280
  elsif options['fallthru']
262
281
  SugarJar::Log.debug("Falling thru to: hub #{ARGV.join(' ')}")
263
- exec('hub', *ARGV)
282
+ if options['github_cli'] == 'hub'
283
+ exec('hub', *ARGV)
284
+ else
285
+ # If we're using 'gh', it doesn't have 'git fall thru' support, so
286
+ # we pass thru directly to 'git'
287
+ exec('git', *ARGV)
288
+ end
264
289
  else
265
290
  SugarJar::Log.error("No such subcommand: #{subcommand}")
266
291
  end
@@ -12,28 +12,34 @@ class SugarJar
12
12
  class Commands
13
13
  include SugarJar::Util
14
14
 
15
+ MAIN_BRANCHES = %w{master main}.freeze
16
+
15
17
  def initialize(options)
16
18
  SugarJar::Log.debug("Commands.initialize options: #{options}")
17
19
  @ghuser = options['github_user']
18
20
  @ghhost = options['github_host']
21
+ @cli = options['github_cli']
19
22
  @ignore_dirty = options['ignore_dirty']
20
23
  @ignore_prerun_failure = options['ignore_prerun_failure']
21
24
  @repo_config = SugarJar::RepoConfig.config
22
25
  @color = options['color']
26
+ @checks = {}
27
+ @main_branch = nil
28
+ @main_remote_branches = {}
23
29
  return if options['no_change']
24
30
 
25
- set_hub_host if @ghhost
31
+ set_hub_host
26
32
  set_commit_template if @repo_config['commit_template']
27
33
  end
28
34
 
29
35
  def feature(name, base = nil)
30
36
  assert_in_repo
31
37
  SugarJar::Log.debug("Feature: #{name}, #{base}")
32
- die("#{name} already exists!") if all_branches.include?(name)
33
- base ||= most_master
38
+ die("#{name} already exists!") if all_local_branches.include?(name)
39
+ base ||= most_main
34
40
  base_pieces = base.split('/')
35
- hub('fetch', base_pieces[0]) if base_pieces.length > 1
36
- hub('checkout', '-b', name, base)
41
+ git('fetch', base_pieces[0]) if base_pieces.length > 1
42
+ git('checkout', '-b', name, base)
37
43
  SugarJar::Log.info(
38
44
  "Created feature branch #{color(name, :green)} based on " +
39
45
  color(base, :green),
@@ -56,9 +62,9 @@ class SugarJar
56
62
  def bcleanall
57
63
  assert_in_repo
58
64
  curr = current_branch
59
- all_branches.each do |branch|
60
- if branch == 'master'
61
- SugarJar::Log.debug('Skipping master')
65
+ all_local_branches.each do |branch|
66
+ if MAIN_BRANCHES.include?(branch)
67
+ SugarJar::Log.debug("Skipping #{branch}")
62
68
  next
63
69
  end
64
70
 
@@ -73,28 +79,28 @@ class SugarJar
73
79
  end
74
80
  end
75
81
 
76
- # Return to the branch we were on, or master
77
- if all_branches.include?(curr)
78
- hub('checkout', curr)
82
+ # Return to the branch we were on, or main
83
+ if all_local_branches.include?(curr)
84
+ git('checkout', curr)
79
85
  else
80
- hub('checkout', 'master')
86
+ checkout_main_branch
81
87
  end
82
88
  end
83
89
 
84
90
  def co(*args)
85
91
  assert_in_repo
86
- s = hub('checkout', *args)
92
+ s = git('checkout', *args)
87
93
  SugarJar::Log.info(s.stderr + s.stdout.chomp)
88
94
  end
89
95
 
90
96
  def br
91
97
  assert_in_repo
92
- SugarJar::Log.info(hub('branch', '-v').stdout.chomp)
98
+ SugarJar::Log.info(git('branch', '-v').stdout.chomp)
93
99
  end
94
100
 
95
101
  def binfo
96
102
  assert_in_repo
97
- SugarJar::Log.info(hub(
103
+ SugarJar::Log.info(git(
98
104
  'log', '--graph', '--oneline', '--decorate', '--boundary',
99
105
  "#{tracked_branch}.."
100
106
  ).stdout.chomp)
@@ -103,9 +109,9 @@ class SugarJar
103
109
  # binfo for all branches
104
110
  def smartlog
105
111
  assert_in_repo
106
- SugarJar::Log.info(hub(
112
+ SugarJar::Log.info(git(
107
113
  'log', '--graph', '--oneline', '--decorate', '--boundary',
108
- '--branches', "#{most_master}.."
114
+ '--branches', "#{most_main}.."
109
115
  ).stdout.chomp)
110
116
  end
111
117
 
@@ -113,13 +119,22 @@ class SugarJar
113
119
 
114
120
  def up
115
121
  assert_in_repo
122
+ # get a copy of our current branch, if rebase fails, we won't
123
+ # be able to determine it without backing out
124
+ curr = current_branch
116
125
  result = gitup
117
- if result
118
- SugarJar::Log.info(
119
- "#{color(current_branch, :green)} rebased on #{result}",
126
+ if result['so'].error?
127
+ die(
128
+ "#{color(curr, :red)}: Failed to rebase on " +
129
+ "#{result['base']}. Leaving the repo as-is. You can get out of " +
130
+ 'this with a `git rebase --abort`. Output from failed rebase is: ' +
131
+ "\nSTDOUT:\n#{result['so'].stdout.lines.map { |x| "\t#{x}" }.join}" +
132
+ "\nSTDERR:\n#{result['so'].stderr.lines.map { |x| "\t#{x}" }.join}",
120
133
  )
121
134
  else
122
- die("#{color(current_branch, :red)}: Failed to rebase")
135
+ SugarJar::Log.info(
136
+ "#{color(current_branch, :green)} rebased on #{result['base']}",
137
+ )
123
138
  end
124
139
  end
125
140
 
@@ -131,28 +146,29 @@ class SugarJar
131
146
 
132
147
  def qamend(*args)
133
148
  assert_in_repo
134
- SugarJar::Log.info(hub('commit', '--amend', '--no-edit', *args).stdout)
149
+ SugarJar::Log.info(git('commit', '--amend', '--no-edit', *args).stdout)
135
150
  end
136
151
 
137
152
  alias amendq qamend
138
153
 
139
154
  def upall
140
155
  assert_in_repo
141
- all_branches.each do |branch|
142
- next if branch == 'master'
156
+ all_local_branches.each do |branch|
157
+ next if MAIN_BRANCHES.include?(branch)
143
158
 
144
- hub('checkout', branch)
159
+ git('checkout', branch)
145
160
  result = gitup
146
- if result
147
- SugarJar::Log.info(
148
- "#{color(branch, :green)} rebased on #{color(result, :green)}",
149
- )
150
- else
161
+ if result['so'].error?
151
162
  SugarJar::Log.error(
152
163
  "#{color(branch, :red)} failed rebase. Reverting attempt and " +
153
- 'moving to next branch',
164
+ 'moving to next branch. Try `sj up` manually on that branch.',
165
+ )
166
+ git('rebase', '--abort')
167
+ else
168
+ SugarJar::Log.info(
169
+ "#{color(branch, :green)} rebased on " +
170
+ color(result['base'], :green).to_s,
154
171
  )
155
- hub('rebase', '--abort')
156
172
  end
157
173
  end
158
174
  end
@@ -164,28 +180,57 @@ class SugarJar
164
180
 
165
181
  reponame = File.basename(repo, '.git')
166
182
  dir ||= reponame
183
+
167
184
  SugarJar::Log.info("Cloning #{reponame}...")
168
- hub('clone', canonicalize_repo(repo), dir, *args)
169
185
 
186
+ # GH's 'fork' command (with the --clone arg) will fork, if necessary,
187
+ # then clone, and then setup the remotes with the appropriate names. So
188
+ # we just let it do all the work for us and return.
189
+ if gh?
190
+ ghcli('repo', 'fork', '--clone', canonicalize_repo(repo), dir, *args)
191
+ SugarJar::Log.info('Remotes "origin" and "upstream" configured.')
192
+ return
193
+ end
194
+
195
+ # For 'hub', first we clone, using git, as 'hub' always needs a repo
196
+ # to operate on.
197
+ git('clone', canonicalize_repo(repo), dir, *args)
198
+
199
+ # Then we go into it and attempt to use the 'fork' capability
170
200
  Dir.chdir dir do
171
201
  # Now that we have a repo, if we have a hub host set it.
172
- set_hub_host if @ghhost
202
+ set_hub_host
173
203
 
174
204
  org = extract_org(repo)
175
205
  SugarJar::Log.debug("Comparing org #{org} to ghuser #{@ghuser}")
176
206
  if org == @ghuser
177
207
  puts 'Cloned forked or self-owned repo. Not creating "upstream".'
208
+ SugarJar::Log.info('Remotes "origin" and "upstream" configured.')
178
209
  return
179
210
  end
180
211
 
181
- s = hub_nofail('fork', '--remote-name=origin')
212
+ s = ghcli_nofail('repo', 'fork', '--remote-name=origin')
182
213
  if s.error?
183
- # if the fork command failed, we already have one, so we have
184
- # to swap the remote names ourselves
185
- # newer 'hub's don't fail and do the right thing...
186
- SugarJar::Log.info("Fork (#{@ghuser}/#{reponame}) detected.")
187
- hub('remote', 'rename', 'origin', 'upstream')
188
- hub('remote', 'add', 'origin', forked_repo(repo, @ghuser))
214
+ if s.stdout.include?('SAML enforcement')
215
+ SugarJar::Log.info(
216
+ 'Forking the repo failed because the repo requires SAML ' +
217
+ "authentication. Full output:\n\n\t#{s.stdout}",
218
+ )
219
+ exit(1)
220
+ else
221
+ # gh as well as old versions of hub, it would fail if the upstream
222
+ # fork already existed. If we got an error, but didn't recognize
223
+ # that, we'll assume that's what happened and try to add the remote
224
+ # ourselves.
225
+ SugarJar::Log.info("Fork (#{@ghuser}/#{reponame}) detected.")
226
+ SugarJar::Log.debug(
227
+ 'The above is a bit of a lie. "hub" failed to fork and it was ' +
228
+ 'not a SAML error, so our best guess is that a fork exists ' +
229
+ 'and so we will try to configure it.',
230
+ )
231
+ git('remote', 'rename', 'origin', 'upstream')
232
+ git('remote', 'add', 'origin', forked_repo(repo, @ghuser))
233
+ end
189
234
  else
190
235
  SugarJar::Log.info("Forked #{reponame} to #{@ghuser}")
191
236
  end
@@ -221,11 +266,15 @@ class SugarJar
221
266
 
222
267
  def version
223
268
  puts "sugarjar version #{SugarJar::VERSION}"
224
- puts hub('version').stdout
269
+ puts ghcli('version').stdout
270
+ # 'hub' prints the 'git' version, but gh doesn't, so if we're on 'gh'
271
+ # print out the git version directly
272
+ puts git('version').stdout if gh?
225
273
  end
226
274
 
227
- def smartpullrequest
275
+ def smartpullrequest(*args)
228
276
  assert_in_repo
277
+ assert_common_main_branch
229
278
  if dirty?
230
279
  SugarJar::Log.warn(
231
280
  'Your repo is dirty, so I am not going to create a pull request. ' +
@@ -233,12 +282,61 @@ class SugarJar
233
282
  )
234
283
  exit(1)
235
284
  end
236
- system(which('hub'), 'pull-request')
285
+ if gh?
286
+ SugarJar::Log.trace("Running: gh pr create #{args.join(' ')}")
287
+ system(which('gh'), 'pr', 'create', *args)
288
+ else
289
+ SugarJar::Log.trace("Running: hub pull-request #{args.join(' ')}")
290
+ system(which('hub'), 'pull-request', *args)
291
+ end
237
292
  end
238
293
 
239
294
  alias spr smartpullrequest
240
295
  alias smartpr smartpullrequest
241
296
 
297
+ def pullsuggestions
298
+ assert_in_repo
299
+
300
+ if dirty?
301
+ if @ignore_dirty
302
+ SugarJar::Log.warn(
303
+ 'Your repo is dirty, but --ignore-dirty was specified, so ' +
304
+ 'carrying on anyway.',
305
+ )
306
+ else
307
+ SugarJar::Log.error(
308
+ 'Your repo is dirty, so I am not going to push. Please commit ' +
309
+ 'or amend first.',
310
+ )
311
+ exit(1)
312
+ end
313
+ end
314
+
315
+ src = "origin/#{current_branch}"
316
+ fetch('origin')
317
+ diff = git('diff', src).stdout
318
+ return unless diff && !diff.empty?
319
+
320
+ puts "Will merge the following suggestions:\n\n#{diff}"
321
+
322
+ loop do
323
+ $stdout.print("\nAre you sure? [y/n] ")
324
+ ans = $stdin.gets.strip
325
+ case ans
326
+ when /^[Yy]$/
327
+ system(which('git'), 'merge', '--ff', "origin/#{current_branch}")
328
+ break
329
+ when /^[Nn]$/, /^[Qq](uit)?/
330
+ puts 'Not merging at user request...'
331
+ break
332
+ else
333
+ puts "Didn't understand '#{ans}'."
334
+ end
335
+ end
336
+ end
337
+
338
+ alias ps pullsuggestions
339
+
242
340
  private
243
341
 
244
342
  def _smartpush(remote, branch, force)
@@ -276,11 +374,11 @@ class SugarJar
276
374
 
277
375
  args = ['push', remote, branch]
278
376
  args << '--force-with-lease' if force
279
- puts hub(*args).stderr
377
+ puts git(*args).stderr
280
378
  end
281
379
 
282
380
  def dirty?
283
- s = hub_nofail('diff', '--quiet')
381
+ s = git_nofail('diff', '--quiet')
284
382
  s.error?
285
383
  end
286
384
 
@@ -317,9 +415,9 @@ class SugarJar
317
415
  end
318
416
 
319
417
  def set_hub_host
320
- return unless in_repo
418
+ return unless hub? && in_repo && @ghhost
321
419
 
322
- s = hub_nofail('config', '--local', '--get', 'hub.host')
420
+ s = git_nofail('config', '--local', '--get', 'hub.host')
323
421
  if s.error?
324
422
  SugarJar::Log.info("Setting repo hub.host = #{@ghhost}")
325
423
  else
@@ -337,7 +435,7 @@ class SugarJar
337
435
  end
338
436
  return
339
437
  end
340
- hub('config', '--local', '--add', 'hub.host', @ghhost)
438
+ git('config', '--local', '--add', 'hub.host', @ghhost)
341
439
  end
342
440
 
343
441
  def set_commit_template
@@ -358,7 +456,7 @@ class SugarJar
358
456
  )
359
457
  end
360
458
 
361
- s = hub_nofail('config', '--local', 'commit.template')
459
+ s = git_nofail('config', '--local', 'commit.template')
362
460
  unless s.error?
363
461
  current = s.stdout.strip
364
462
  if current == @repo_config['commit_template']
@@ -376,18 +474,60 @@ class SugarJar
376
474
  'Setting repo-specific commit template to ' +
377
475
  "#{@repo_config['commit_template']} per sugarjar repo config.",
378
476
  )
379
- hub(
477
+ git(
380
478
  'config', '--local', 'commit.template', @repo_config['commit_template']
381
479
  )
382
480
  end
383
481
 
384
- def run_check(type)
385
- unless @repo_config[type]
386
- SugarJar::Log.debug("No #{type} configured. Returning success")
387
- return true
482
+ def get_checks_from_command(type)
483
+ return nil unless @repo_config["#{type}_list_cmd"]
484
+
485
+ cmd = @repo_config["#{type}_list_cmd"]
486
+ short = cmd.split.first
487
+ unless File.exist?(short)
488
+ SugarJar::Log.error(
489
+ "Configured #{type}_list_cmd #{short} does not exist!",
490
+ )
491
+ return false
492
+ end
493
+ s = Mixlib::ShellOut.new(cmd).run_command
494
+ if s.error?
495
+ SugarJar::Log.error(
496
+ "#{type}_list_cmd (#{cmd}) failed: #{s.format_for_exception}",
497
+ )
498
+ return false
499
+ end
500
+ s.stdout.split("\n")
501
+ end
502
+
503
+ # determine if we're using the _list_cmd and if so run it to get the
504
+ # checks, or just use the directly-defined check, and cache it
505
+ def get_checks(type)
506
+ return @checks[type] if @checks[type]
507
+
508
+ ret = get_checks_from_command(type)
509
+ if ret
510
+ SugarJar::Log.debug("Found #{type}s: #{ret}")
511
+ @checks[type] = ret
512
+ # if it's explicitly false, we failed to run the command
513
+ elsif ret == false
514
+ @checks[type] = false
515
+ # otherwise, we move on (basically: it's nil, there was no _list_cmd)
516
+ else
517
+ SugarJar::Log.debug("[#{type}]: using listed linters: #{ret}")
518
+ @checks[type] = @repo_config[type] || []
388
519
  end
520
+ @checks[type]
521
+ end
522
+
523
+ def run_check(type)
389
524
  Dir.chdir repo_root do
390
- @repo_config[type].each do |check|
525
+ checks = get_checks(type)
526
+ # if we failed to determine the checks, the the checks have effectively
527
+ # failed
528
+ return false unless checks
529
+
530
+ checks.each do |check|
391
531
  SugarJar::Log.debug("Running #{type} #{check}")
392
532
 
393
533
  short = check.split.first
@@ -405,7 +545,7 @@ class SugarJar
405
545
  SugarJar::Log.warn(
406
546
  "The linter modified the repo. Here's the diff:\n",
407
547
  )
408
- puts hub('diff').stdout
548
+ puts git('diff').stdout
409
549
  loop do
410
550
  $stdout.print(
411
551
  "\nWould you like to\n\t[q]uit and inspect\n\t[a]mend the " +
@@ -457,29 +597,82 @@ class SugarJar
457
597
  exit(1)
458
598
  end
459
599
 
600
+ def assert_common_main_branch
601
+ upstream_branch = main_remote_branch(upstream)
602
+ unless main_branch == upstream_branch
603
+ die(
604
+ "The local main branch is '#{main_branch}', but the main branch " +
605
+ "of the #{upstream} remote is '#{upstream_branch}'. You probably " +
606
+ "want to rename your local branch by doing:\n\t" +
607
+ "git branch -m #{main_branch} #{upstream_branch}\n\t" +
608
+ "git fetch #{upstream}\n\t" +
609
+ "git branch -u #{upstream}/#{upstream_branch} #{upstream_branch}\n" +
610
+ "\tgit remote set-head #{upstream} -a",
611
+ )
612
+ end
613
+ return if upstream_branch == 'origin'
614
+
615
+ origin_branch = main_remote_branch('origin')
616
+ return if origin_branch == upstream_branch
617
+
618
+ die(
619
+ "The main branch of your upstream (#{upstream_branch}) and your " +
620
+ "fork/origin (#{origin_branch}) are not the same. You should go " +
621
+ "to https://#{@ghhost || 'github.com'}/#{@ghuser}/#{repo_name}/" +
622
+ 'branches/ and rename the \'default\' branch to ' +
623
+ "'#{upstream_branch}'. It will then give you some commands to " +
624
+ 'run to update this clone.',
625
+ )
626
+ end
627
+
460
628
  def assert_in_repo
461
629
  die('sugarjar must be run from inside a git repo') unless in_repo
462
630
  end
463
631
 
632
+ def determine_main_branch(branches)
633
+ branches.include?('main') ? 'main' : 'master'
634
+ end
635
+
636
+ def main_branch
637
+ @main_branch = determine_main_branch(all_local_branches)
638
+ end
639
+
640
+ def main_remote_branch(remote)
641
+ @main_remote_branches[remote] ||=
642
+ determine_main_branch(all_remote_branches(remote))
643
+ end
644
+
645
+ def checkout_main_branch
646
+ git('checkout', main_branch)
647
+ end
648
+
464
649
  def clean_branch(name)
465
- die('Cannot remove master branch') if name == 'master'
650
+ die("Cannot remove #{name} branch") if MAIN_BRANCHES.include?(name)
466
651
  SugarJar::Log.debug('Fetch relevant remote...')
467
652
  fetch_upstream
468
653
  return false unless safe_to_clean(name)
469
654
 
470
655
  SugarJar::Log.debug('branch deemed safe to delete...')
471
- hub('checkout', 'master')
472
- hub('branch', '-D', name)
656
+ checkout_main_branch
657
+ git('branch', '-D', name)
473
658
  gitup
474
659
  true
475
660
  end
476
661
 
477
- def all_branches
662
+ def all_remote_branches(remote = 'origin')
478
663
  branches = []
479
- hub('branch', '--format', '%(refname)').stdout.lines.each do |line|
480
- next if line == 'master'
664
+ git('branch', '-r', '--format', '%(refname)').stdout.lines.each do |line|
665
+ next unless line.start_with?("refs/remotes/#{remote}/")
666
+
667
+ branches << branch_from_ref(line.strip, :remote)
668
+ end
669
+ branches
670
+ end
481
671
 
482
- branches << line.strip.split('/')[2]
672
+ def all_local_branches
673
+ branches = []
674
+ git('branch', '--format', '%(refname)').stdout.lines.each do |line|
675
+ branches << branch_from_ref(line.strip)
483
676
  end
484
677
  branches
485
678
  end
@@ -488,7 +681,7 @@ class SugarJar
488
681
  # cherry -v will output 1 line per commit on the target branch
489
682
  # prefixed by a - or + - anything with a - can be dropped, anything
490
683
  # else cannot.
491
- out = hub(
684
+ out = git(
492
685
  'cherry', '-v', tracked_branch, branch
493
686
  ).stdout.lines.reject do |line|
494
687
  line.start_with?('-')
@@ -502,24 +695,24 @@ class SugarJar
502
695
 
503
696
  # if the "easy" check didn't work, it's probably because there
504
697
  # was a squash-merge. To check for that we make our own squash
505
- # merge to upstream/master and see if that has any delta
698
+ # merge to upstream/main and see if that has any delta
506
699
 
507
700
  # First we need a temp branch to work on
508
701
  tmpbranch = "_sugar_jar.#{Process.pid}"
509
702
 
510
- hub('checkout', '-b', tmpbranch, tracked_branch)
511
- s = hub_nofail('merge', '--squash', branch)
703
+ git('checkout', '-b', tmpbranch, tracked_branch)
704
+ s = git_nofail('merge', '--squash', branch)
512
705
  if s.error?
513
706
  cleanup_tmp_branch(tmpbranch, branch)
514
707
  SugarJar::Log.debug(
515
- 'Failed to merge changes into current master. This means we could ' +
708
+ 'Failed to merge changes into current main. This means we could ' +
516
709
  'not figure out if this is merged or not. Check manually and use ' +
517
710
  "'git branch -D #{branch}' if it is safe to do so.",
518
711
  )
519
712
  return false
520
713
  end
521
714
 
522
- s = hub('diff', '--staged')
715
+ s = git('diff', '--staged')
523
716
  out = s.stdout
524
717
  SugarJar::Log.debug("Squash-merged diff: #{out}")
525
718
  cleanup_tmp_branch(tmpbranch, branch)
@@ -530,34 +723,37 @@ class SugarJar
530
723
  true
531
724
  else
532
725
  SugarJar::Log.debug(
533
- 'After squash-merging, this branch is NOT fully merged to master',
726
+ 'After squash-merging, this branch is NOT fully merged to main',
534
727
  )
535
728
  false
536
729
  end
537
730
  end
538
731
 
539
732
  def cleanup_tmp_branch(tmp, backto)
540
- hub('reset', '--hard', tracked_branch)
541
- hub('checkout', backto)
542
- hub('branch', '-D', tmp)
733
+ git('reset', '--hard', tracked_branch)
734
+ git('checkout', backto)
735
+ git('branch', '-D', tmp)
543
736
  end
544
737
 
545
738
  def current_branch
546
- hub('symbolic-ref', 'HEAD').stdout.strip.split('/')[2]
739
+ branch_from_ref(git('symbolic-ref', 'HEAD').stdout.strip)
547
740
  end
548
741
 
549
742
  def fetch_upstream
550
743
  us = upstream
551
- hub('fetch', us) if us
744
+ fetch(us) if us
745
+ end
746
+
747
+ def fetch(remote)
748
+ git('fetch', remote)
552
749
  end
553
750
 
554
751
  def gitup
555
752
  SugarJar::Log.debug('Fetching upstream')
556
753
  fetch_upstream
557
754
  curr = current_branch
558
- SugarJar::Log.debug('Rebasing')
559
755
  base = tracked_branch
560
- if curr != 'master' && base == "origin/#{curr}"
756
+ if !MAIN_BRANCHES.include?(curr) && base == "origin/#{curr}"
561
757
  SugarJar::Log.warn(
562
758
  "This branch is tracking origin/#{curr}, which is probably your " +
563
759
  'downstream (where you push _to_) as opposed to your upstream ' +
@@ -566,34 +762,38 @@ class SugarJar
566
762
  'to do a "git branch -u upstream".',
567
763
  )
568
764
  end
569
- s = hub_nofail('rebase', base)
570
- s.error? ? nil : base
765
+ SugarJar::Log.debug('Rebasing')
766
+ s = git_nofail('rebase', base)
767
+ {
768
+ 'so' => s,
769
+ 'base' => base,
770
+ }
571
771
  end
572
772
 
573
773
  def tracked_branch
574
- s = hub_nofail(
774
+ s = git_nofail(
575
775
  'rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'
576
776
  )
577
777
  if s.error?
578
- most_master
778
+ most_main
579
779
  else
580
780
  s.stdout.strip
581
781
  end
582
782
  end
583
783
 
584
- def most_master
784
+ def most_main
585
785
  us = upstream
586
786
  if us
587
- "#{us}/master"
787
+ "#{us}/#{main_branch}"
588
788
  else
589
- master
789
+ main_branch
590
790
  end
591
791
  end
592
792
 
593
793
  def upstream
594
794
  return @remote if @remote
595
795
 
596
- s = hub('remote')
796
+ s = git('remote')
597
797
 
598
798
  remotes = s.stdout.lines.map(&:strip)
599
799
  SugarJar::Log.debug("remotes is #{remotes}")
@@ -611,6 +811,13 @@ class SugarJar
611
811
  @remote
612
812
  end
613
813
 
814
+ def branch_from_ref(ref, type = :local)
815
+ # local branches are refs/head/XXXX
816
+ # remote branches are refs/remotes/<remote>/XXXX
817
+ base = type == :local ? 2 : 3
818
+ ref.split('/')[base..].join('/')
819
+ end
820
+
614
821
  def color(string, *colors)
615
822
  if @color
616
823
  pastel.decorate(string, *colors)
@@ -625,5 +832,21 @@ class SugarJar
625
832
  Pastel.new
626
833
  end
627
834
  end
835
+
836
+ def hub?
837
+ @cli == 'hub'
838
+ end
839
+
840
+ def gh?
841
+ @cli == 'gh'
842
+ end
843
+
844
+ def ghcli_nofail(*args)
845
+ gh? ? gh_nofail(*args) : hub_nofail(*args)
846
+ end
847
+
848
+ def ghcli(*args)
849
+ gh? ? gh(*args) : hub(*args)
850
+ end
628
851
  end
629
852
  end
@@ -6,14 +6,15 @@ class SugarJar
6
6
  # This is stuff like log level, github-user, etc.
7
7
  class Config
8
8
  DEFAULTS = {
9
- 'github_user' => ENV['USER'],
9
+ 'github_cli' => 'hub',
10
+ 'github_user' => ENV.fetch('USER'),
10
11
  'fallthru' => true,
11
12
  }.freeze
12
13
 
13
14
  def self._find_ordered_files
14
15
  [
15
16
  '/etc/sugarjar/config.yaml',
16
- "#{ENV['HOME']}/.config/sugarjar/config.yaml",
17
+ "#{Dir.home}/.config/sugarjar/config.yaml",
17
18
  ].select { |f| File.exist?(f) }
18
19
  end
19
20
 
data/lib/sugarjar/util.rb CHANGED
@@ -26,11 +26,25 @@ class SugarJar
26
26
  exit(1)
27
27
  end
28
28
 
29
- def hub_nofail(*args)
29
+ def git_nofail(*args)
30
30
  if %w{diff log grep branch}.include?(args[0]) &&
31
31
  args.none? { |x| x.include?('color') }
32
32
  args << (@color ? '--color' : '--no-color')
33
33
  end
34
+ SugarJar::Log.trace("Running: git #{args.join(' ')}")
35
+ Mixlib::ShellOut.new([which('git')] + args).run_command
36
+ end
37
+
38
+ def git(*args)
39
+ s = git_nofail(*args)
40
+ s.error!
41
+ s
42
+ end
43
+
44
+ def hub_nofail(*args)
45
+ # this allows us to use 'hub' stuff that's top-level, but is under
46
+ # repo for this.
47
+ args.delete_at(0) if args[0] == 'repo'
34
48
  SugarJar::Log.trace("Running: hub #{args.join(' ')}")
35
49
  s = Mixlib::ShellOut.new([which('hub')] + args).run_command
36
50
  if s.error?
@@ -84,13 +98,43 @@ class SugarJar
84
98
  s
85
99
  end
86
100
 
101
+ def gh_nofail(*args)
102
+ SugarJar::Log.trace("Running: gh #{args.join(' ')}")
103
+ s = Mixlib::ShellOut.new([which('gh')] + args).run_command
104
+ if s.error? && s.stderr.include?('gh auth')
105
+ SugarJar::Log.info(
106
+ 'gh was run but no github token exists. Will run "gh auth login" ' +
107
+ "to force\ngh to authenticate...",
108
+ )
109
+ unless system(which('gh'), 'auth', 'login', '-p', 'ssh')
110
+ SugarJar::Log.fatal(
111
+ 'That failed, I will bail out. Hub needs to get a github ' +
112
+ 'token. Try running "gh auth login" (will list info about ' +
113
+ 'your account) and try this again when that works.',
114
+ )
115
+ exit(1)
116
+ end
117
+ end
118
+ s
119
+ end
120
+
121
+ def gh(*args)
122
+ s = gh_nofail(*args)
123
+ s.error!
124
+ s
125
+ end
126
+
87
127
  def in_repo
88
- s = hub_nofail('rev-parse', '--is-inside-work-tree')
128
+ s = git_nofail('rev-parse', '--is-inside-work-tree')
89
129
  !s.error? && s.stdout.strip == 'true'
90
130
  end
91
131
 
92
132
  def repo_root
93
- hub('rev-parse', '--show-toplevel').stdout.strip
133
+ git('rev-parse', '--show-toplevel').stdout.strip
134
+ end
135
+
136
+ def repo_name
137
+ repo_root.split('/').last
94
138
  end
95
139
  end
96
140
  end
@@ -1,3 +1,3 @@
1
1
  class SugarJar
2
- VERSION = '0.0.9'.freeze
2
+ VERSION = '0.0.11'.freeze
3
3
  end
data/sugarjar.gemspec CHANGED
@@ -20,4 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.add_dependency 'mixlib-log'
21
21
  spec.add_dependency 'mixlib-shellout'
22
22
  spec.add_dependency 'pastel'
23
+ spec.metadata = {
24
+ 'rubygems_mfa_required' => 'true',
25
+ }
23
26
  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: 0.0.9
4
+ version: 0.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Phil Dibowitz
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-21 00:00:00.000000000 Z
11
+ date: 2022-10-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mixlib-log
@@ -52,7 +52,7 @@ dependencies:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
- description:
55
+ description:
56
56
  email:
57
57
  - phil@ipom.com
58
58
  executables:
@@ -78,8 +78,9 @@ files:
78
78
  homepage: https://github.com/jaymzh/sugarjar
79
79
  licenses:
80
80
  - Apache-2.0
81
- metadata: {}
82
- post_install_message:
81
+ metadata:
82
+ rubygems_mfa_required: 'true'
83
+ post_install_message:
83
84
  rdoc_options: []
84
85
  require_paths:
85
86
  - lib
@@ -94,8 +95,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
95
  - !ruby/object:Gem::Version
95
96
  version: '0'
96
97
  requirements: []
97
- rubygems_version: 3.2.5
98
- signing_key:
98
+ rubygems_version: 3.3.7
99
+ signing_key:
99
100
  specification_version: 4
100
101
  summary: A git/github helper script
101
102
  test_files: []