sugarjar 0.0.4 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e9576a717ef65aa68a0a7f46e883245fc7e288a9884924658484f68e5d78470
4
- data.tar.gz: 420299217d7e184c8129634351f5b9dc3ee336207d9c4a5ee3dd3d5ea64b0c31
3
+ metadata.gz: 7b12b221b467eca9aeba30396d059870a82608120ce1739ded50b4aef55ce7ba
4
+ data.tar.gz: c84bcab24de60fc61a0241f7c2940d6da6d78bc7416fa3fb92500528a447f9c8
5
5
  SHA512:
6
- metadata.gz: 0d68dfb58e071f078ac63e3781db4dbea649a95ddbf0d9afce7b1361d74baa6ea0264df426260d3124f6a23a89cddeafc3efd83bc52670fb5210d388f2c28adb
7
- data.tar.gz: c226c00b7332803a686d82cd2a916212f09c476c50afbe7ae7f0a95e510d59ba2ae2bd553fbaa0af3601f01e90ef5c63e6b56fde13b38ff180553a3c1f48e0a0
6
+ metadata.gz: e041cd65eb939ea1d2153bf13a8a019e05f194fcf6ce4326c69d62a2de9e34e1d036cf759c5ea17b5b42d8e7e159fc99d65ee75470580a4ed15a854fffc7c87f
7
+ data.tar.gz: 2c47c2920b6a774ffb83ef16761183ac7dbfcc808a6885335bbeeee5470a29ceff343df4e3748897ab4e8e681e7c7d8b9a5fc3c7ed4d44fafaa40dda6c08b300
data/Gemfile CHANGED
@@ -5,5 +5,6 @@ gem 'sugarjar', :path => '.'
5
5
 
6
6
  group :test do
7
7
  gem 'mdl'
8
+ gem 'rspec'
8
9
  gem 'rubocop'
9
10
  end
data/README.md CHANGED
@@ -1,19 +1,21 @@
1
1
  # SugarJar
2
2
 
3
- ![CI](https://github.com/jaymzh/sugarjar/workflows/CI/badge.svg)
3
+ [![Lint](https://github.com/jaymzh/sugarjar/workflows/Lint/badge.svg)](https://github.com/jaymzh/sugarjar/actions?query=workflow%3ALint)
4
+ [![Unittest](https://github.com/jaymzh/sugarjar/workflows/Unittests/badge.svg)](https://github.com/jaymzh/sugarjar/actions?query=workflow%3AUnittests)
5
+ [![DCO](https://github.com/jaymzh/sugarjar/workflows/DCO%20Check/badge.svg)](https://github.com/jaymzh/sugarjar/actions?query=workflow%3A%22DCO+Check%22)
4
6
  [![Gem Version](https://badge.fury.io/rb/sugarjar.svg)](https://badge.fury.io/rb/sugarjar)
5
7
 
6
8
  Welcome to SugarJar - a git/github helper. It leverages the amazing GitHub cli,
7
9
  [hub](https://hub.github.com/), so you'll need that installed.
8
10
 
9
11
  SugarJar is inspired by [arcanist](https://github.com/phacility/arcanist), and
10
- it's replacement at Facebook, JellyFish. Many of the features they provide for
12
+ its replacement at Facebook, JellyFish. Many of the features they provide for
11
13
  the Phabricator workflow this aims to bring to the GitHub workflow.
12
14
 
13
15
  In particular there are a lot of helpers for using a squash-merge workflow that
14
16
  is poorly handled by the standard toolsets.
15
17
 
16
- If you miss Mondrian or Phabrictor - this is the tool for you!
18
+ If you miss Mondrian or Phabricator - this is the tool for you!
17
19
 
18
20
  If you don't, there's a ton of useful stuff for everyone!
19
21
 
@@ -21,12 +23,12 @@ If you don't, there's a ton of useful stuff for everyone!
21
23
 
22
24
  It is common for a PR to go back and forth with a variety of nits, lint fixes,
23
25
  typos, etc. that can muddy history. So many projects will "squash and merge"
24
- when they accept a pull request. Howevet, that means `git branch -d <branch>`
26
+ when they accept a pull request. However, that means `git branch -d <branch>`
25
27
  doesn't work. Git will tell you the branch isn't fully merged. You can, of
26
28
  course `git branch -D <branch>`, but that does no safety checks at all, it
27
29
  forces the deletion.
28
30
 
29
- Enter `sj bclean` - it determines of the contents of your branch has been merge
31
+ Enter `sj bclean` - it determines if the contents of your branch has been merge
30
32
  and safely deletes if so.
31
33
 
32
34
  ``` shell
@@ -74,7 +76,7 @@ This will:
74
76
  Note that it takes `hub`s short-names for repos. No need to specify a full URL,
75
77
  just a $org/$repo.
76
78
 
77
- Like `git clone`, `sj sclone` will accept an additional arguement as the
79
+ Like `git clone`, `sj sclone` will accept an additional argument as the
78
80
  destination directory to clone to. It will also pass any other unknown options
79
81
  to `git clone` under the hood.
80
82
 
@@ -118,7 +120,7 @@ small lint issue? Not anymore! SJ can be configured to run things before
118
120
  pushing. For example,in the SugarJar repo, we have it run Rubocop (ruby lint)
119
121
  and Markdownlint "on_push". If those fail, it lets you know and doesn't push.
120
122
 
121
- You can configure SugarJar to tell how how to run both lints and unittests for
123
+ You can configure SugarJar to tell it how to run both lints and unittests for
122
124
  a given repo and if one or both should be run prior to pushing.
123
125
 
124
126
  The details on the config file format is below, but we provide three commands:
@@ -145,7 +147,7 @@ push if any of them fail.
145
147
  ## Better push defaults
146
148
 
147
149
  In addition to running pre-push tests for you `smartpush` also picks smart
148
- defaults for push. So if you `sj spush` with no arguements, it uses the
150
+ defaults for push. So if you `sj spush` with no arguments, it uses the
149
151
  `origin` remote and the same branch name you're on as the remote branch.
150
152
 
151
153
  ## Cleaning up your own history
@@ -156,11 +158,11 @@ combination of rebases, amends and force pushes. We provide two commands here
156
158
  to help.
157
159
 
158
160
  The first is pretty straight forward and is basically just an alias: `sj
159
- amend`. It will ammend whatever you want to the most recent commit (just an
161
+ amend`. It will amend whatever you want to the most recent commit (just an
160
162
  alias for `git commit --amend`). It has a partner `qamend` (or `amendq` if you
161
163
  prefer) that will do so without prompting to update your commit message.
162
164
 
163
- So now you've rebased or amended, pushing becomes challening. You can `git push
165
+ So now you've rebased or amended, pushing becomes challenging. You can `git push
164
166
  --force`, but everyone knows that's incredibly dangerous. Is there a better
165
167
  way? There is! Git provides `git push --force-with-lease` - it checks to make
166
168
  sure you're up-to-date with the remote before forcing the push. But man that
@@ -174,7 +176,7 @@ When you want to start a new feature, you want to start developing against
174
176
  latest. That's why `sj feature` defaults to creating a branch against what we
175
177
  call "most master". That is, `upstream/master` if it exists, otherwise
176
178
  `origin/master` if that exists, otherwise `master`. You can pass in an
177
- additional arguement to base it off of something else.
179
+ additional argument to base it off of something else.
178
180
 
179
181
  ```shell
180
182
  $ git branch
@@ -189,6 +191,13 @@ $ sj feature dependent-feature test-branch
189
191
  Created feature branch dependent-feature based on test-branch
190
192
  ```
191
193
 
194
+ ## Smartlog
195
+
196
+ Smartlog will show you a tree diagram of your branches! Simply run `sj
197
+ smartlog` or `sj sl` for short.
198
+
199
+ ![smartlog screenshot](https://github.com/jaymzh/sugarjar/blob/master/smartlog.png)
200
+
192
201
  ## And more!
193
202
 
194
203
  See `sj help` for more commands!
@@ -246,17 +255,19 @@ troubleshoot configuration parsing.
246
255
  ## Repository Configuration
247
256
 
248
257
  Sugarjar looks for a `.sugarjar.yaml` in the root of the repository to tell it
249
- how to handle repo-specific things. Currently there are only three
250
- configurations accepted:
258
+ how to handle repo-specific things. Currently there options are:
251
259
 
252
- * lint - A list of scripts to run on `sj lint`. These should be linters like
260
+ * `lint` - A list of scripts to run on `sj lint`. These should be linters like
253
261
  rubocop or pyflake.
254
- * unit - A list of scripts to run on `sj unit`. These should be unittest
262
+ * `unit` - A list of scripts to run on `sj unit`. These should be unittest
255
263
  runners like rspec or pyunit.
256
- * on_push - A list of types (`lint`, `unit`) of checks to run before pushing.
264
+ * `on_push` - A list of types (`lint`, `unit`) of checks to run before pushing.
257
265
  It is highly recommended this is only `lint`. The goal here is to allow for
258
266
  the user to get quick stylistic feedback before pushing their branch to avoid
259
267
  the push-fix-push-fix loop.
268
+ * `commit_template` - A path to a commit template to set in the `commit.template`
269
+ git config for this repo. Should be either a fully-qualified path, or a path
270
+ relative to the repo root.
260
271
 
261
272
  Example configuration:
262
273
 
@@ -267,8 +278,17 @@ unit:
267
278
  - scripts/unit
268
279
  on_push:
269
280
  - lint
281
+ commit_template: .commit-template.txt
270
282
  ```
271
283
 
284
+ ### Commit Templates
285
+
286
+ While GitHub provides a way to specify a pull-request template by putting the
287
+ right file into a repo, there is no way to tell git to automatically pick up a
288
+ commit template by dropping a file in the repo. Users must do something like:
289
+ `git config commit.template <file>`. Making each developer do this is error
290
+ prone, so this setting will automatically set this up for each developer.
291
+
272
292
  ## Enterprise GitHub
273
293
 
274
294
  Like `hub`, SugarJar supports GitHub Enterprise. In fact, we provide extra
data/bin/sj CHANGED
@@ -13,7 +13,7 @@ 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 = { 'color' => true }
17
17
  # If ENV['SUGARJAR_DEBUG'] is set, it overrides the config file,
18
18
  # but not the command line options, so set that one here. Also
19
19
  # start the logger at that level, in case we are debugging option loading
@@ -53,6 +53,20 @@ parser = OptionParser.new do |opts|
53
53
  exit
54
54
  end
55
55
 
56
+ opts.on(
57
+ '--ignore-dirty',
58
+ 'Tell command that check for a dirty repo to carry on anyway.',
59
+ ) do
60
+ options['ignore_dirty'] = true
61
+ end
62
+
63
+ opts.on(
64
+ '--ignore-prerun-failure',
65
+ 'Ignore preprun failure on *push commands.',
66
+ ) do
67
+ options['ignore_prerun_failure'] = true
68
+ end
69
+
56
70
  opts.on(
57
71
  '--log-level LEVEL',
58
72
  'Set logging level (fatal, error, warning, info, debug, trace). Default: ' +
@@ -61,6 +75,10 @@ parser = OptionParser.new do |opts|
61
75
  options['log_level'] = level
62
76
  end
63
77
 
78
+ opts.on('--[no-]use-color', 'Enable color. [default: true]') do |color|
79
+ options['color'] = color
80
+ end
81
+
64
82
  opts.on('--version') do
65
83
  puts SugarJar::VERSION
66
84
  exit
@@ -119,6 +137,15 @@ COMMANDS:
119
137
  your account (if not already done) and then setup your remotes
120
138
  so that "origin" is your fork and "upstream" is the upstream.
121
139
 
140
+ smartlog, sl
141
+ Inspired by Facebook's "sl" extension to Mercurial, this command
142
+ will show you a tree of all your local branches relative to your
143
+ upstream.
144
+
145
+ smartpullrequest, smartpr, spr
146
+ A smart wrapper to "hub pull-request" that checks if your repo
147
+ is dirty before creating the pull request.
148
+
122
149
  smartpush, spush
123
150
  A smart wrapper to "git push" that runs whatever is defined in
124
151
  "on_push" in .sugarjar.yml, and only pushes if they succeed.
@@ -132,7 +159,7 @@ COMMANDS:
132
159
  upall
133
160
  Same as "up", but for all branches.
134
161
 
135
- verson
162
+ version
136
163
  Print the version of sugarjar, and then run 'hub version'
137
164
  to show the hub and git versions.
138
165
  COMMANDS
@@ -144,7 +171,10 @@ end
144
171
  # we parse later. We also need a pristine copy in case we want to
145
172
  # run git as we were called.
146
173
  argv_copy = ARGV.dup
147
- sj = SugarJar::Commands.new(options)
174
+
175
+ # We don't have options yet, but we need an instance of SJ in order
176
+ # to list public methods. We will recreate it
177
+ sj = SugarJar::Commands.new(options.merge({ 'no_change' => true }))
148
178
  extra_opts = []
149
179
 
150
180
  # as with above, this can't go into 'options', until after we parse
@@ -204,7 +234,10 @@ if ARGV.empty?
204
234
  end
205
235
 
206
236
  options = config.merge(options)
237
+
238
+ # Recreate SJ with all of our options
207
239
  SugarJar::Log.level = options['log_level'].to_sym if options['log_level']
240
+ sj = SugarJar::Commands.new(options)
208
241
 
209
242
  subcommand = argv_copy.reject { |x| x.start_with?('-') }.first
210
243
  argv_copy.delete(subcommand)
@@ -221,7 +254,9 @@ if subcommand == 'help'
221
254
  end
222
255
 
223
256
  if is_valid_command
224
- SugarJar::Log.debug("running #{subcommand}, #{extra_opts.join(', ')}")
257
+ SugarJar::Log.debug(
258
+ "running #{subcommand}; extra opts: #{extra_opts.join(', ')}",
259
+ )
225
260
  sj.send(subcommand.to_sym, *extra_opts)
226
261
  elsif options['fallthru']
227
262
  SugarJar::Log.debug("Falling thru to: hub #{ARGV.join(' ')}")
@@ -1,3 +1,5 @@
1
+ require 'mixlib/shellout'
2
+
1
3
  require_relative 'util'
2
4
  require_relative 'repoconfig'
3
5
  require_relative 'log'
@@ -11,10 +13,17 @@ class SugarJar
11
13
  include SugarJar::Util
12
14
 
13
15
  def initialize(options)
16
+ SugarJar::Log.debug("Commands.initialize options: #{options}")
14
17
  @ghuser = options['github_user']
15
18
  @ghhost = options['github_host']
19
+ @ignore_dirty = options['ignore_dirty']
20
+ @ignore_prerun_failure = options['ignore_prerun_failure']
16
21
  @repo_config = SugarJar::RepoConfig.config
22
+ @color = options['color']
23
+ return if options['no_change']
24
+
17
25
  set_hub_host if @ghhost
26
+ set_commit_template if @repo_config['commit_template']
18
27
  end
19
28
 
20
29
  def feature(name, base = nil)
@@ -25,42 +34,62 @@ class SugarJar
25
34
  base_pieces = base.split('/')
26
35
  hub('fetch', base_pieces[0]) if base_pieces.length > 1
27
36
  hub('checkout', '-b', name, base)
28
- SugarJar::Log.info("Created feature branch #{name} based on #{base}")
37
+ SugarJar::Log.info(
38
+ "Created feature branch #{color(name, :green)} based on " +
39
+ color(base, :green),
40
+ )
29
41
  end
30
42
 
31
43
  def bclean(name = nil)
32
44
  assert_in_repo
33
45
  name ||= current_branch
34
- # rubocop:disable Style/GuardClause
35
- unless clean_branch(name)
36
- die("Cannot clean #{name} - there are unmerged commits")
46
+ if clean_branch(name)
47
+ SugarJar::Log.info("#{name}: #{color('reaped', :green)}")
48
+ else
49
+ die(
50
+ "#{color("Cannot clean #{name}", :red)}! there are unmerged " +
51
+ "commits; use 'git branch -D #{name}' to forcefully delete it.",
52
+ )
37
53
  end
38
- # rubocop:enable Style/GuardClause
39
54
  end
40
55
 
41
56
  def bcleanall
42
57
  assert_in_repo
58
+ curr = current_branch
43
59
  all_branches.each do |branch|
44
- next if branch == 'master'
60
+ if branch == 'master'
61
+ SugarJar::Log.debug('Skipping master')
62
+ next
63
+ end
45
64
 
46
- # rubocop:disable Style/Next
47
- unless clean_branch(branch)
48
- SugarJar::Log.info(
49
- "Skipping branch #{branch} - there are unmerged commits",
65
+ if clean_branch(branch)
66
+ SugarJar::Log.info("#{branch}: #{color('reaped', :green)}")
67
+ else
68
+ SugarJar::Log.info("#{branch}: skipped")
69
+ SugarJar::Log.debug(
70
+ "There are unmerged commits; use 'git branch -D #{branch}' to " +
71
+ 'forcefully delete it)',
50
72
  )
51
73
  end
52
- # rubocop:enable Style/Next
74
+ end
75
+
76
+ # Return to the branch we were on, or master
77
+ if all_branches.include?(curr)
78
+ hub('checkout', curr)
79
+ else
80
+ hub('checkout', 'master')
53
81
  end
54
82
  end
55
83
 
56
84
  def co(*args)
57
85
  assert_in_repo
58
- hub('checkout', *args)
86
+ s = hub('checkout', *args)
87
+ SugarJar::Log.info(s.stderr + s.stdout.chomp)
59
88
  end
60
89
 
61
90
  def br
62
91
  assert_in_repo
63
- puts hub('branch', '-v').stdout
92
+ SugarJar::Log.info(hub('branch', '-v').stdout.chomp)
64
93
  end
65
94
 
66
95
  def binfo
@@ -68,16 +97,29 @@ class SugarJar
68
97
  SugarJar::Log.info(hub(
69
98
  'log', '--graph', '--oneline', '--decorate', '--boundary',
70
99
  "#{tracked_branch}.."
71
- ).stdout)
100
+ ).stdout.chomp)
72
101
  end
73
102
 
103
+ # binfo for all branches
104
+ def smartlog
105
+ assert_in_repo
106
+ SugarJar::Log.info(hub(
107
+ 'log', '--graph', '--oneline', '--decorate', '--boundary',
108
+ '--branches', "#{most_master}.."
109
+ ).stdout.chomp)
110
+ end
111
+
112
+ alias sl smartlog
113
+
74
114
  def up
75
115
  assert_in_repo
76
116
  result = gitup
77
117
  if result
78
- SugarJar::Log.info("Rebased branch on #{result}")
118
+ SugarJar::Log.info(
119
+ "#{color(current_branch, :green)} rebased on #{result}",
120
+ )
79
121
  else
80
- die('Failed to rebase current branch')
122
+ die("#{color(current_branch, :red)}: Failed to rebase")
81
123
  end
82
124
  end
83
125
 
@@ -102,11 +144,13 @@ class SugarJar
102
144
  hub('checkout', branch)
103
145
  result = gitup
104
146
  if result
105
- SugarJar::Log.info("Rebased #{branch} on #{result}")
147
+ SugarJar::Log.info(
148
+ "#{color(branch, :green)} rebased on #{color(result, :green)}",
149
+ )
106
150
  else
107
151
  SugarJar::Log.error(
108
- "Failed to rebase #{branch}, aborting that and moving to next " +
109
- 'branch',
152
+ "#{color(branch, :red)} failed rebase. Reverting attempt and " +
153
+ 'moving to next branch',
110
154
  )
111
155
  hub('rebase', '--abort')
112
156
  end
@@ -121,15 +165,16 @@ class SugarJar
121
165
  reponame = File.basename(repo, '.git')
122
166
  dir ||= reponame
123
167
  SugarJar::Log.info("Cloning #{reponame}...")
124
- hub('clone', repo, dir, *args)
168
+ hub('clone', canonicalize_repo(repo), dir, *args)
125
169
 
126
170
  Dir.chdir dir do
127
171
  # Now that we have a repo, if we have a hub host set it.
128
172
  set_hub_host if @ghhost
129
173
 
130
- org = File.basename(File.dirname(repo))
174
+ org = extract_org(repo)
175
+ SugarJar::Log.debug("Comparing org #{org} to ghuser #{@ghuser}")
131
176
  if org == @ghuser
132
- put 'Cloned forked or self-owned repo. Not creating "upstream".'
177
+ puts 'Cloned forked or self-owned repo. Not creating "upstream".'
133
178
  return
134
179
  end
135
180
 
@@ -137,9 +182,10 @@ class SugarJar
137
182
  if s.error?
138
183
  # if the fork command failed, we already have one, so we have
139
184
  # to swap the remote names ourselves
185
+ # newer 'hub's don't fail and do the right thing...
140
186
  SugarJar::Log.info("Fork (#{@ghuser}/#{reponame}) detected.")
141
187
  hub('remote', 'rename', 'origin', 'upstream')
142
- hub('remote', 'add', 'origin', repo.gsub("#{org}/", "#{@ghuser}/"))
188
+ hub('remote', 'add', 'origin', forked_repo(repo, @ghuser))
143
189
  else
144
190
  SugarJar::Log.info("Forked #{reponame} to #{@ghuser}")
145
191
  end
@@ -150,38 +196,25 @@ class SugarJar
150
196
  alias sclone smartclone
151
197
 
152
198
  def lint
199
+ assert_in_repo
153
200
  exit(1) unless run_check('lint')
154
201
  end
155
202
 
156
203
  def unit
157
- exit(1) unless run_check('lint')
204
+ assert_in_repo
205
+ exit(1) unless run_check('unit')
158
206
  end
159
207
 
160
208
  def smartpush(remote = nil, branch = nil)
161
- unless remote && branch
162
- remote ||= 'origin'
163
- branch ||= current_branch
164
- end
165
-
166
- if run_prepush
167
- puts hub('push', remote, branch).stderr
168
- else
169
- SugarJar::Log.error('Pre-push checks failed. Not pushing.')
170
- end
209
+ assert_in_repo
210
+ _smartpush(remote, branch, false)
171
211
  end
172
212
 
173
213
  alias spush smartpush
174
214
 
175
215
  def forcepush(remote = nil, branch = nil)
176
- unless remote && branch
177
- remote ||= 'origin'
178
- branch ||= current_branch
179
- end
180
- if run_prepush
181
- puts hub('push', '--force-with-lease', remote, branch).stderr
182
- else
183
- SugarJar::Log.error('Pre-push checks failed. Not pushing.')
184
- end
216
+ assert_in_repo
217
+ _smartpush(remote, branch, true)
185
218
  end
186
219
 
187
220
  alias fpush forcepush
@@ -191,8 +224,98 @@ class SugarJar
191
224
  puts hub('version').stdout
192
225
  end
193
226
 
227
+ def smartpullrequest
228
+ assert_in_repo
229
+ if dirty?
230
+ SugarJar::Log.warn(
231
+ 'Your repo is dirty, so I am not going to create a pull request. ' +
232
+ 'You should commit or amend and push it to your remote first.',
233
+ )
234
+ exit(1)
235
+ end
236
+ system(which('hub'), 'pull-request')
237
+ end
238
+
239
+ alias spr smartpullrequest
240
+ alias smartpr smartpullrequest
241
+
194
242
  private
195
243
 
244
+ def _smartpush(remote, branch, force)
245
+ unless remote && branch
246
+ remote ||= 'origin'
247
+ branch ||= current_branch
248
+ end
249
+
250
+ if dirty?
251
+ if @ignore_dirty
252
+ SugarJar::Log.warn(
253
+ 'Your repo is dirty, but --ignore-dirty was specified, so ' +
254
+ 'carrying on anyway.',
255
+ )
256
+ else
257
+ SugarJar::Log.error(
258
+ 'Your repo is dirty, so I am not going to push. Please commit ' +
259
+ 'or amend first.',
260
+ )
261
+ exit(1)
262
+ end
263
+ end
264
+
265
+ unless run_prepush
266
+ if @ignore_prerun_failure
267
+ SugarJar::Log.warn(
268
+ 'Pre-push checks failed, but --ignore-prerun-failure was ' +
269
+ 'specified, so carrying on anyway',
270
+ )
271
+ else
272
+ SugarJar::Log.error('Pre-push checks failed. Not pushing.')
273
+ exit(1)
274
+ end
275
+ end
276
+
277
+ args = ['push', remote, branch]
278
+ args << '--force-with-lease' if force
279
+ puts hub(*args).stderr
280
+ end
281
+
282
+ def dirty?
283
+ s = hub_nofail('diff', '--quiet')
284
+ s.error?
285
+ end
286
+
287
+ def extract_org(repo)
288
+ if repo.start_with?('http')
289
+ File.basename(File.dirname(repo))
290
+ elsif repo.start_with?('git@')
291
+ repo.split(':')[1].split('/')[0]
292
+ else
293
+ # assume they passed in a hub-friendly name
294
+ repo.split('/').first
295
+ end
296
+ end
297
+
298
+ def forked_repo(repo, username)
299
+ repo = if repo.start_with?('http', 'git@')
300
+ File.basename(repo)
301
+ else
302
+ "#{File.basename(repo)}.git"
303
+ end
304
+ "git@#{@ghhost || 'github.com'}:#{username}/#{repo}"
305
+ end
306
+
307
+ # Hub will default to https, but we should always default to SSH
308
+ # unless otherwise specified since https will cause prompting.
309
+ def canonicalize_repo(repo)
310
+ # if they fully-qualified it, we're good
311
+ return repo if repo.start_with?('http', 'git@')
312
+
313
+ # otherwise, ti's a shortname
314
+ cr = "git@#{@ghhost || 'github.com'}:#{repo}.git"
315
+ SugarJar::Log.debug("canonicalized #{repo} to #{cr}")
316
+ cr
317
+ end
318
+
196
319
  def set_hub_host
197
320
  return unless in_repo
198
321
 
@@ -201,7 +324,7 @@ class SugarJar
201
324
  SugarJar::Log.info("Setting repo hub.host = #{@ghhost}")
202
325
  else
203
326
  current = s.stdout
204
- if current == @ghost
327
+ if current == @ghhost
205
328
  SugarJar::Log.debug('Repo hub.host already set correctly')
206
329
  else
207
330
  # Even though we have an explicit config, in most cases, it
@@ -217,6 +340,47 @@ class SugarJar
217
340
  hub('config', '--local', '--add', 'hub.host', @ghhost)
218
341
  end
219
342
 
343
+ def set_commit_template
344
+ unless in_repo
345
+ SugarJar::Log.debug('Skipping set_commit_template: not in repo')
346
+ return
347
+ end
348
+
349
+ realpath = if @repo_config['commit_template'].start_with?('/')
350
+ @repo_config['commit_template']
351
+ else
352
+ "#{repo_root}/#{@repo_config['commit_template']}"
353
+ end
354
+ unless File.exist?(realpath)
355
+ die(
356
+ "Repo config specifies #{@repo_config['commit_template']} as the " +
357
+ 'commit template, but that file does not exist.',
358
+ )
359
+ end
360
+
361
+ s = hub_nofail('config', '--local', 'commit.template')
362
+ unless s.error?
363
+ current = s.stdout.strip
364
+ if current == @repo_config['commit_template']
365
+ SugarJar::Log.debug('Commit template already set correctly')
366
+ return
367
+ else
368
+ SugarJar::Log.warn(
369
+ "Updating repo-specific commit template from #{current} " +
370
+ "to #{@repo_config['commit_template']}",
371
+ )
372
+ end
373
+ end
374
+
375
+ SugarJar::Log.debug(
376
+ 'Setting repo-specific commit template to ' +
377
+ "#{@repo_config['commit_template']} per sugarjar repo config.",
378
+ )
379
+ hub(
380
+ 'config', '--local', 'commit.template', @repo_config['commit_template']
381
+ )
382
+ end
383
+
220
384
  def run_check(type)
221
385
  unless @repo_config[type]
222
386
  SugarJar::Log.debug("No #{type} configured. Returning success")
@@ -224,17 +388,55 @@ class SugarJar
224
388
  end
225
389
  Dir.chdir repo_root do
226
390
  @repo_config[type].each do |check|
227
- SugarJar::Log.info("Running #{type} #{check}")
391
+ SugarJar::Log.debug("Running #{type} #{check}")
228
392
 
229
- unless File.exist?(check)
230
- SugarJar::Log.error("Configured #{type} #{check} does not exist!")
393
+ short = check.split.first
394
+ unless File.exist?(short)
395
+ SugarJar::Log.error("Configured #{type} #{short} does not exist!")
231
396
  return false
232
397
  end
233
398
  s = Mixlib::ShellOut.new(check).run_command
399
+
400
+ # Linters auto-correct, lets handle that gracefully
401
+ if type == 'lint' && dirty?
402
+ SugarJar::Log.info(
403
+ "[#{type}] #{short}: #{color('Corrected', :yellow)}",
404
+ )
405
+ SugarJar::Log.warn(
406
+ "The linter modified the repo. Here's the diff:\n",
407
+ )
408
+ puts hub('diff').stdout
409
+ loop do
410
+ $stdout.print(
411
+ "\nWould you like to\n\t[q]uit and inspect\n\t[a]mend the " +
412
+ "changes to the current commit and re-run\n > ",
413
+ )
414
+ ans = $stdin.gets.strip
415
+ case ans
416
+ when /^q/
417
+ SugarJar::Log.info('Exiting at user request.')
418
+ exit(1)
419
+ when /^a/
420
+ qamend('-a')
421
+ # break here, if we get out of this loop we 'redo', assuming
422
+ # the user chose this option
423
+ break
424
+ end
425
+ end
426
+ redo
427
+ end
428
+
234
429
  if s.error?
235
- SugarJar::Log.info("#{type} #{check} failed\n#{s.stdout}")
430
+ SugarJar::Log.info(
431
+ "[#{type}] #{short} #{color('failed', :red)}, output follows " +
432
+ "(see debug for more)\n#{s.stdout}",
433
+ )
434
+ SugarJar::Log.debug(s.format_for_exception)
236
435
  return false
237
436
  end
437
+ SugarJar::Log.info(
438
+ "[#{type}] #{short}: #{color('OK', :green)}",
439
+ )
238
440
  end
239
441
  end
240
442
  end
@@ -243,7 +445,7 @@ class SugarJar
243
445
  @repo_config['on_push']&.each do |item|
244
446
  SugarJar::Log.debug("Running on_push check type #{item}")
245
447
  unless send(:run_check, item)
246
- SugarJar::Log.info("Push check #{item} failed.")
448
+ SugarJar::Log.info("[prepush]: #{item} #{color('failed', :red)}.")
247
449
  return false
248
450
  end
249
451
  end
@@ -269,7 +471,6 @@ class SugarJar
269
471
  hub('checkout', 'master')
270
472
  hub('branch', '-D', name)
271
473
  gitup
272
- SugarJar::Log.info("Reaped branch #{name}")
273
474
  true
274
475
  end
275
476
 
@@ -310,7 +511,7 @@ class SugarJar
310
511
  s = hub_nofail('merge', '--squash', branch)
311
512
  if s.error?
312
513
  cleanup_tmp_branch(tmpbranch, branch)
313
- SugarJar::Log.error(
514
+ SugarJar::Log.debug(
314
515
  'Failed to merge changes into current master. This means we could ' +
315
516
  'not figure out if this is merged or not. Check manually and use ' +
316
517
  "'git branch -D #{branch}' if it is safe to do so.",
@@ -353,8 +554,18 @@ class SugarJar
353
554
  def gitup
354
555
  SugarJar::Log.debug('Fetching upstream')
355
556
  fetch_upstream
557
+ curr = current_branch
356
558
  SugarJar::Log.debug('Rebasing')
357
559
  base = tracked_branch
560
+ if curr != 'master' && base == "origin/#{curr}"
561
+ SugarJar::Log.warn(
562
+ "This branch is tracking origin/#{curr}, which is probably your " +
563
+ 'downstream (where you push _to_) as opposed to your upstream ' +
564
+ '(where you pull _from_). This means that "sj up" is probably ' +
565
+ 'rebasing on the wrong thing and doing nothing. You probably want ' +
566
+ 'to do a "git branch -u upstream".',
567
+ )
568
+ end
358
569
  s = hub_nofail('rebase', base)
359
570
  s.error? ? nil : base
360
571
  end
@@ -399,5 +610,20 @@ class SugarJar
399
610
  end
400
611
  @remote
401
612
  end
613
+
614
+ def color(string, *colors)
615
+ if @color
616
+ pastel.decorate(string, *colors)
617
+ else
618
+ string
619
+ end
620
+ end
621
+
622
+ def pastel
623
+ @pastel ||= begin
624
+ require 'pastel'
625
+ Pastel.new
626
+ end
627
+ end
402
628
  end
403
629
  end
@@ -6,14 +6,14 @@ class SugarJar
6
6
  # This is stuff like log level, github-user, etc.
7
7
  class Config
8
8
  DEFAULTS = {
9
- 'ghuser' => ENV['USER'],
9
+ 'github_user' => ENV['USER'],
10
10
  'fallthru' => true,
11
11
  }.freeze
12
12
 
13
13
  def self._find_ordered_files
14
14
  [
15
15
  '/etc/sugarjar/config.yaml',
16
- "#{ENV['HOME']}/.config/sugarjar/config.yaml"
16
+ "#{ENV['HOME']}/.config/sugarjar/config.yaml",
17
17
  ].select { |f| File.exist?(f) }
18
18
  end
19
19
 
data/lib/sugarjar/util.rb CHANGED
@@ -27,8 +27,55 @@ class SugarJar
27
27
  end
28
28
 
29
29
  def hub_nofail(*args)
30
+ if %w{diff log grep branch}.include?(args[0]) &&
31
+ args.none? { |x| x.include?('color') }
32
+ args << (@color ? '--color' : '--no-color')
33
+ end
30
34
  SugarJar::Log.trace("Running: hub #{args.join(' ')}")
31
- Mixlib::ShellOut.new([which('hub')] + args).run_command
35
+ s = Mixlib::ShellOut.new([which('hub')] + args).run_command
36
+ if s.error?
37
+ # depending on hub version and possibly other things, STDERR
38
+ # is either "Requires authentication" or "Must authenticate"
39
+ case s.stderr
40
+ when /^(Must|Requires) authenticat/
41
+ SugarJar::Log.info(
42
+ 'Hub was run but no github token exists. Will run "hub api user" ' +
43
+ "to force\nhub to authenticate...",
44
+ )
45
+ unless system(which('hub'), 'api', 'user')
46
+ SugarJar::Log.fatal(
47
+ 'That failed, I will bail out. Hub needs to get a github ' +
48
+ 'token. Try running "hub api user" (will list info about ' +
49
+ 'your account) and try this again when that works.',
50
+ )
51
+ exit(1)
52
+ end
53
+ SugarJar::Log.info('Re-running original hub command...')
54
+ s = Mixlib::ShellOut.new([which('hub')] + args).run_command
55
+ when /^fatal: could not read Username/, /Anonymous access denied/
56
+
57
+ # On http(s) URLs, git may prompt for username/passwd
58
+ SugarJar::Log.info(
59
+ 'Hub was run but git prompted for authentication. This probably ' +
60
+ "means you have\nused an http repo URL instead of an ssh one. It " +
61
+ "is recommended you reclone\nusing 'sj sclone' to setup your " +
62
+ "remotes properly. However, in the meantime,\nwe'll go ahead " +
63
+ "and re-run the command in a shell so you can type in the\n" +
64
+ 'credentials.',
65
+ )
66
+ unless system(which('hub'), *args)
67
+ SugarJar::Log.fatal(
68
+ 'That failed, I will bail out. You can either manually change ' +
69
+ 'your remotes, or simply create a fresh clone with ' +
70
+ '"sj smartclone".',
71
+ )
72
+ exit(1)
73
+ end
74
+ SugarJar::Log.info('Re-running original hub command...')
75
+ s = Mixlib::ShellOut.new([which('hub')] + args).run_command
76
+ end
77
+ end
78
+ s
32
79
  end
33
80
 
34
81
  def hub(*args)
@@ -1,3 +1,3 @@
1
1
  class SugarJar
2
- VERSION = '0.0.4'.freeze
2
+ VERSION = '0.0.9'.freeze
3
3
  end
data/sugarjar.gemspec CHANGED
@@ -8,6 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.email = ['phil@ipom.com']
9
9
  spec.license = 'Apache-2.0'
10
10
  spec.homepage = 'https://github.com/jaymzh/sugarjar'
11
+ spec.required_ruby_version = '>= 2.6.0'
11
12
  docs = %w{README.md LICENSE Gemfile sugarjar.gemspec}
12
13
  spec.extra_rdoc_files = docs
13
14
  spec.executables << 'sj'
@@ -18,4 +19,5 @@ Gem::Specification.new do |spec|
18
19
 
19
20
  spec.add_dependency 'mixlib-log'
20
21
  spec.add_dependency 'mixlib-shellout'
22
+ spec.add_dependency 'pastel'
21
23
  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.4
4
+ version: 0.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Phil Dibowitz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-06-17 00:00:00.000000000 Z
11
+ date: 2021-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mixlib-log
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pastel
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  description:
42
56
  email:
43
57
  - phil@ipom.com
@@ -73,14 +87,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
73
87
  requirements:
74
88
  - - ">="
75
89
  - !ruby/object:Gem::Version
76
- version: '0'
90
+ version: 2.6.0
77
91
  required_rubygems_version: !ruby/object:Gem::Requirement
78
92
  requirements:
79
93
  - - ">="
80
94
  - !ruby/object:Gem::Version
81
95
  version: '0'
82
96
  requirements: []
83
- rubygems_version: 3.0.3
97
+ rubygems_version: 3.2.5
84
98
  signing_key:
85
99
  specification_version: 4
86
100
  summary: A git/github helper script