sugarjar 0.0.2 → 0.0.7

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: c9a62438fa0d483eb64cd28cb5de3e603d6907a353194fac8df0bc8cd51a55f1
4
- data.tar.gz: ff816417124253671a07ffe4ee2c6cb066a3b8f3f6dc0bac439a667297b595bf
3
+ metadata.gz: 9fefe2bc9c94d95d3c869f8636bbabf433ec6196ae8d68b7ac131b4a955a882b
4
+ data.tar.gz: 5237f94836f686a0e82a8a18543b54179e7190ce874d481e59ae5fe685aa26d9
5
5
  SHA512:
6
- metadata.gz: a97436c810c10af47d507eeb8c29070da38927884aef6f6ffaa39a9476777ba88e4c176fdb69ab67a979c832c3d32339f1c848c97ef1f6ecd5dcf17025f2b9e0
7
- data.tar.gz: 478d20bcc6c8ed561f2ab6294d38a0e3267a96f0e239a565e498b483505dff950ad1712931a777012141e955438bb5f082db75698eb478a36981666bba38af19
6
+ metadata.gz: d8ade9a12c3ba79a5ba591123179a80aeff6db7cbd4ab48c42cc27ecd5f5f4c3bcd713b99a04654b7c28e4fca715c3f6535f9938a97f3c55fc41169791f8d6a6
7
+ data.tar.gz: cbe9733235652a2ee5d777e53eed4a0f7deb9ffdcb69b0825b89c7831c52e5317e1e854b61e038d1a27fd92c85326e560c287e9e162d3068abcd246bc0e9ee48
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'appbundler'
4
+ gem 'sugarjar', :path => '.'
5
+
6
+ group :test do
7
+ gem 'mdl'
8
+ gem 'rspec'
9
+ gem 'rubocop'
10
+ end
data/README.md CHANGED
@@ -1,6 +1,8 @@
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
+ [![DCO](https://github.com/jaymzh/sugarjar/workflows/DCO%20Check/badge.svg)](https://github.com/jaymzh/sugarjar/actions?query=workflow%3A%22DCO+Check%22)
5
+ [![Gem Version](https://badge.fury.io/rb/sugarjar.svg)](https://badge.fury.io/rb/sugarjar)
4
6
 
5
7
  Welcome to SugarJar - a git/github helper. It leverages the amazing GitHub cli,
6
8
  [hub](https://hub.github.com/), so you'll need that installed.
@@ -12,7 +14,7 @@ the Phabricator workflow this aims to bring to the GitHub workflow.
12
14
  In particular there are a lot of helpers for using a squash-merge workflow that
13
15
  is poorly handled by the standard toolsets.
14
16
 
15
- If you miss Mondrian or Phabrictor - this is the tool for you!
17
+ If you miss Mondrian or Phabricator - this is the tool for you!
16
18
 
17
19
  If you don't, there's a ton of useful stuff for everyone!
18
20
 
@@ -20,7 +22,7 @@ If you don't, there's a ton of useful stuff for everyone!
20
22
 
21
23
  It is common for a PR to go back and forth with a variety of nits, lint fixes,
22
24
  typos, etc. that can muddy history. So many projects will "squash and merge"
23
- when they accept a pull request. Howevet, that means `git branch -d <branch>`
25
+ when they accept a pull request. However, that means `git branch -d <branch>`
24
26
  doesn't work. Git will tell you the branch isn't fully merged. You can, of
25
27
  course `git branch -D <branch>`, but that does no safety checks at all, it
26
28
  forces the deletion.
@@ -73,7 +75,7 @@ This will:
73
75
  Note that it takes `hub`s short-names for repos. No need to specify a full URL,
74
76
  just a $org/$repo.
75
77
 
76
- Like `git clone`, `sj sclone` will accept an additional arguement as the
78
+ Like `git clone`, `sj sclone` will accept an additional argument as the
77
79
  destination directory to clone to. It will also pass any other unknown options
78
80
  to `git clone` under the hood.
79
81
 
@@ -144,7 +146,7 @@ push if any of them fail.
144
146
  ## Better push defaults
145
147
 
146
148
  In addition to running pre-push tests for you `smartpush` also picks smart
147
- defaults for push. So if you `sj spush` with no arguements, it uses the
149
+ defaults for push. So if you `sj spush` with no arguments, it uses the
148
150
  `origin` remote and the same branch name you're on as the remote branch.
149
151
 
150
152
  ## Cleaning up your own history
@@ -155,11 +157,11 @@ combination of rebases, amends and force pushes. We provide two commands here
155
157
  to help.
156
158
 
157
159
  The first is pretty straight forward and is basically just an alias: `sj
158
- amend`. It will ammend whatever you want to the most recent commit (just an
160
+ amend`. It will amend whatever you want to the most recent commit (just an
159
161
  alias for `git commit --amend`). It has a partner `qamend` (or `amendq` if you
160
162
  prefer) that will do so without prompting to update your commit message.
161
163
 
162
- So now you've rebased or amended, pushing becomes challening. You can `git push
164
+ So now you've rebased or amended, pushing becomes challenging. You can `git push
163
165
  --force`, but everyone knows that's incredibly dangerous. Is there a better
164
166
  way? There is! Git provides `git push --force-with-lease` - it checks to make
165
167
  sure you're up-to-date with the remote before forcing the push. But man that
@@ -173,7 +175,7 @@ When you want to start a new feature, you want to start developing against
173
175
  latest. That's why `sj feature` defaults to creating a branch against what we
174
176
  call "most master". That is, `upstream/master` if it exists, otherwise
175
177
  `origin/master` if that exists, otherwise `master`. You can pass in an
176
- additional arguement to base it off of something else.
178
+ additional argument to base it off of something else.
177
179
 
178
180
  ```shell
179
181
  $ git branch
@@ -245,17 +247,19 @@ troubleshoot configuration parsing.
245
247
  ## Repository Configuration
246
248
 
247
249
  Sugarjar looks for a `.sugarjar.yaml` in the root of the repository to tell it
248
- how to handle repo-specific things. Currently there are only three
249
- configurations accepted:
250
+ how to handle repo-specific things. Currently there options are:
250
251
 
251
- * lint - A list of scripts to run on `sj lint`. These should be linters like
252
+ * `lint` - A list of scripts to run on `sj lint`. These should be linters like
252
253
  rubocop or pyflake.
253
- * unit - A list of scripts to run on `sj unit`. These should be unittest
254
+ * `unit` - A list of scripts to run on `sj unit`. These should be unittest
254
255
  runners like rspec or pyunit.
255
- * on_push - A list of types (`lint`, `unit`) of checks to run before pushing.
256
+ * `on_push` - A list of types (`lint`, `unit`) of checks to run before pushing.
256
257
  It is highly recommended this is only `lint`. The goal here is to allow for
257
258
  the user to get quick stylistic feedback before pushing their branch to avoid
258
259
  the push-fix-push-fix loop.
260
+ * `commit_template` - A path to a commit template to set in the `commit.template`
261
+ git config for this repo. Should be either a fully-qualified path, or a path
262
+ relative to the repo root.
259
263
 
260
264
  Example configuration:
261
265
 
@@ -266,8 +270,17 @@ unit:
266
270
  - scripts/unit
267
271
  on_push:
268
272
  - lint
273
+ commit_template: .commit-template.txt
269
274
  ```
270
275
 
276
+ ### Commit Templates
277
+
278
+ While GitHub provides a way to specify a pull-request template by putting the
279
+ right file into a repo, there is no way to tell git to automatically pick up a
280
+ commit template by dropping a file in the repo. Users must do something like:
281
+ `git config commit.template <file>`. Making each developer do this is error
282
+ prone, so this setting will automatically set this up for each developer.
283
+
271
284
  ## Enterprise GitHub
272
285
 
273
286
  Like `hub`, SugarJar supports GitHub Enterprise. In fact, we provide extra
data/bin/sj ADDED
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env ruby
2
+ # SugarJar
3
+
4
+ require 'optparse'
5
+ require 'mixlib/shellout'
6
+ require_relative '../lib/sugarjar/commands'
7
+ require_relative '../lib/sugarjar/config'
8
+ require_relative '../lib/sugarjar/log'
9
+ require_relative '../lib/sugarjar/util'
10
+ require_relative '../lib/sugarjar/version'
11
+
12
+ SugarJar::Log.level = Logger::INFO
13
+
14
+ # Don't put defaults here, put them in SugarJar::Config - otherwise
15
+ # these defaults overwrite whatever is in config files.
16
+ options = {}
17
+ # If ENV['SUGARJAR_DEBUG'] is set, it overrides the config file,
18
+ # but not the command line options, so set that one here. Also
19
+ # start the logger at that level, in case we are debugging option loading
20
+ # itself
21
+ if ENV['SUGARJAR_LOGLEVEL']
22
+ options['log_level'] = SugarJar::Log.level = ENV['SUGARJAR_LOGLEVEL'].to_sym
23
+ end
24
+ parser = OptionParser.new do |opts|
25
+ opts.banner = 'Usage: sj <command> [<args>] [<options>]'
26
+
27
+ opts.separator ''
28
+ opts.separator 'Command, args, and options, can appear in any order.'
29
+ opts.separator ''
30
+ opts.separator 'OPTIONS:'
31
+
32
+ opts.on('--[no-]fallthru', 'Fall-thru to git') do |fallthru|
33
+ options['fallthru'] = fallthru
34
+ end
35
+
36
+ opts.on('--github-user USER', 'Github username') do |user|
37
+ options['github_user'] = user
38
+ end
39
+
40
+ opts.on(
41
+ '--github-host HOST',
42
+ 'The host for "hub". Note that we will set this in the local repo ' +
43
+ 'config so there is no need to have multiple config files for multiple ' +
44
+ 'github servers. Put your default one in your config file, and simply ' +
45
+ 'specify this option the first time you clone or touch a repo and it ' +
46
+ 'will be part of that repo until changed.',
47
+ ) do |host|
48
+ options['github_host'] = host
49
+ end
50
+
51
+ opts.on('-h', '--help', 'Print this help message') do
52
+ puts opts
53
+ exit
54
+ end
55
+
56
+ opts.on(
57
+ '--log-level LEVEL',
58
+ 'Set logging level (fatal, error, warning, info, debug, trace). Default: ' +
59
+ 'info',
60
+ ) do |level|
61
+ options['log_level'] = level
62
+ end
63
+
64
+ opts.on(
65
+ '--ignore-dirty',
66
+ 'Tell command that check for a dirty repo to carry on anyway.',
67
+ ) do
68
+ options['ignore_dirty'] = true
69
+ end
70
+
71
+ opts.on(
72
+ '--ignore-prerun-failure',
73
+ 'Ignore preprun failure on *push commands.',
74
+ ) do
75
+ options['ignore_prerun_failure'] = true
76
+ end
77
+
78
+ opts.on('--version') do
79
+ puts SugarJar::VERSION
80
+ exit
81
+ end
82
+
83
+ # rubocop:disable Layout/HeredocIndentation
84
+ opts.separator <<COMMANDS
85
+
86
+ COMMANDS:
87
+ amend
88
+ Amend the current commit. Alias for "git commit --amend".
89
+ Accepts other arguments such as "-a" or files.
90
+
91
+ amendq, qamend
92
+ Same as "amend" but without changing the message. Alias for
93
+ "git commit --amend --no-edit".
94
+
95
+ bclean
96
+ If safe, delete the current branch. Unlike "git branch -d",
97
+ bclean can handle squash-merged branches. Think of it as
98
+ a smarter "git branch -d".
99
+
100
+ bcleanall
101
+ Walk all branches, and try to delete them if it's safe. See
102
+ "bclean" for details.
103
+
104
+ binfo
105
+ Verbose information about the current branch.
106
+
107
+ br
108
+ Verbose branch list. An alias for "git branch -v".
109
+
110
+ feature
111
+ Create a "feature" branch. It's morally equivalent to
112
+ "git checkout -b" except it defaults to creating it based on
113
+ some form of 'master' instead of your current branch. In order
114
+ of preference it will be upstream/master, origin/master, master,
115
+ depending upon what remotes are available.
116
+
117
+ forcepush, fpush
118
+ The same as "smartpush", but uses "--force-with-lease". This is
119
+ a "safer" way of doing force-pushes and is the recommended way
120
+ to push after rebasing or amending. Never do this to shared
121
+ branches. Very convenient for keeping the branch behind a pull-
122
+ request clean.
123
+
124
+ lint
125
+ Run any linters configured in .sugarjar.yaml.
126
+
127
+ smartclone, sclone
128
+ A smart wrapper to "git clone" that handles forking and managing
129
+ remotes for you.
130
+ It will clone a git repository using hub-style short name
131
+ ("$org/$repo"). If the org of the repository is not the same
132
+ as your github-user then it will fork the repo for you to
133
+ your account (if not already done) and then setup your remotes
134
+ so that "origin" is your fork and "upstream" is the upstream.
135
+
136
+ smartpullrequest, smartpr, spr
137
+ A smart wrapper to "hub pull-request" that checks if your repo
138
+ is dirty before creating the pull request.
139
+
140
+ smartpush, spush
141
+ A smart wrapper to "git push" that runs whatever is defined in
142
+ "on_push" in .sugarjar.yml, and only pushes if they succeed.
143
+
144
+ unit
145
+ Run any unitests configured in .sugarjar.yaml.
146
+
147
+ up
148
+ Rebase the current branch on upstream/master or origin/master.
149
+
150
+ upall
151
+ Same as "up", but for all branches.
152
+
153
+ version
154
+ Print the version of sugarjar, and then run 'hub version'
155
+ to show the hub and git versions.
156
+ COMMANDS
157
+
158
+ # rubocop:enable Layout/HeredocIndentation
159
+ end
160
+
161
+ # we make a copy of these because we will assign back to the ARGV
162
+ # we parse later. We also need a pristine copy in case we want to
163
+ # run git as we were called.
164
+ argv_copy = ARGV.dup
165
+
166
+ # We don't have options yet, but we need an instance of SJ in order
167
+ # to list public methods. We will recreate it
168
+ sj = SugarJar::Commands.new(options.merge({ 'no_change' => true }))
169
+ extra_opts = []
170
+
171
+ # as with above, this can't go into 'options', until after we parse
172
+ # the command line args
173
+ config = SugarJar::Config.config
174
+
175
+ valid_commands = sj.public_methods - Object.public_methods
176
+
177
+ is_valid_command = ARGV.any? { |arg| valid_commands.include?(arg.to_s.to_sym) }
178
+
179
+ # if we're configured to fall thru and the subcommand isn't one
180
+ # we recognize, don't parse the options as they may be different
181
+ # than git's. For example `git config -l` - we error because we
182
+ # require an arguement to `-l`.
183
+ if config['fallthru'] && !is_valid_command
184
+ SugarJar::Log.debug(
185
+ 'Skipping option parsing: fall-thru is set and we do not recognize ' +
186
+ 'any subcommands',
187
+ )
188
+ else
189
+ # We want to allow people to pass in extra args to be passed to
190
+ # git commands, but OptionParser doesn't easily allow this. So we
191
+ # loop over it, catching exceptions.
192
+ begin
193
+ # HOWEVER, anytime it throws an exception, for some reason, it clears
194
+ # out all of ARGV, or whatever you passed to as ARGV.
195
+ #
196
+ # This not only prevents further parsing, but also means we lose
197
+ # any non-option arguements (like the subcommand!)
198
+ #
199
+ # So we save a copy, and if we throw an exception, save the option that
200
+ # caused it, remove that option from our copy, and then re-populate argv
201
+ # with what's left.
202
+ #
203
+ # By doing this we not only get to parse all the options properly and
204
+ # save unknown ones, but non-option arguements, which OptionParser
205
+ # normally leaves in ARGV stay in ARGV.
206
+ saved_argv = argv_copy.dup
207
+ parser.parse!(argv_copy)
208
+ rescue OptionParser::InvalidOption => e
209
+ SugarJar::Log.debug("Saving unknown argument #{e.args}")
210
+ extra_opts += e.args
211
+
212
+ # e.args is an array, but it's only ever one arguement per exception
213
+ saved_argv.delete(e.args.first)
214
+ argv_copy = saved_argv.dup
215
+ SugarJar::Log.debug(
216
+ "Continuing option parsing with remaining ARGV: #{argv_copy}",
217
+ )
218
+ retry
219
+ end
220
+ end
221
+
222
+ if ARGV.empty?
223
+ puts parser
224
+ exit
225
+ end
226
+
227
+ options = config.merge(options)
228
+
229
+ # Recreate SJ with all of our options
230
+ SugarJar::Log.level = options['log_level'].to_sym if options['log_level']
231
+ sj = SugarJar::Commands.new(options)
232
+
233
+ subcommand = argv_copy.reject { |x| x.start_with?('-') }.first
234
+ argv_copy.delete(subcommand)
235
+ SugarJar::Log.debug("subcommand is #{subcommand}")
236
+
237
+ # Extra options we got, plus any left over arguements are what we
238
+ # pass to Commands so they can be passed to git as necessary
239
+ extra_opts += argv_copy
240
+ SugarJar::Log.debug("extra unknown options: #{extra_opts}")
241
+
242
+ if subcommand == 'help'
243
+ puts parser
244
+ exit
245
+ end
246
+
247
+ if is_valid_command
248
+ SugarJar::Log.debug(
249
+ "running #{subcommand}; extra opts: #{extra_opts.join(', ')}",
250
+ )
251
+ sj.send(subcommand.to_sym, *extra_opts)
252
+ elsif options['fallthru']
253
+ SugarJar::Log.debug("Falling thru to: hub #{ARGV.join(' ')}")
254
+ exec('hub', *ARGV)
255
+ else
256
+ SugarJar::Log.error("No such subcommand: #{subcommand}")
257
+ end
@@ -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,16 @@ 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
+ return if options['no_change']
23
+
17
24
  set_hub_host if @ghhost
25
+ set_commit_template if @repo_config['commit_template']
18
26
  end
19
27
 
20
28
  def feature(name, base = nil)
@@ -33,24 +41,36 @@ class SugarJar
33
41
  name ||= current_branch
34
42
  # rubocop:disable Style/GuardClause
35
43
  unless clean_branch(name)
36
- die("Cannot clean #{name} - there are unmerged commits")
44
+ die(
45
+ "Cannot clean #{name} - there are unmerged commits; use " +
46
+ "'git branch -D #{name}' to forcefully delete it.",
47
+ )
37
48
  end
38
49
  # rubocop:enable Style/GuardClause
39
50
  end
40
51
 
41
52
  def bcleanall
42
53
  assert_in_repo
54
+ curr = current_branch
43
55
  all_branches.each do |branch|
44
56
  next if branch == 'master'
45
57
 
46
58
  # rubocop:disable Style/Next
47
59
  unless clean_branch(branch)
48
60
  SugarJar::Log.info(
49
- "Skipping branch #{branch} - there are unmerged commits",
61
+ "Skipping branch #{branch} - there are unmerged commits; use " +
62
+ "'git branch -D #{branch}' to forcefully delete it.",
50
63
  )
51
64
  end
52
65
  # rubocop:enable Style/Next
53
66
  end
67
+
68
+ # Return to the branch we were on, or master
69
+ if all_branches.include?(curr)
70
+ hub('checkout', curr)
71
+ else
72
+ hub('checkout', 'master')
73
+ end
54
74
  end
55
75
 
56
76
  def co(*args)
@@ -127,9 +147,10 @@ class SugarJar
127
147
  # Now that we have a repo, if we have a hub host set it.
128
148
  set_hub_host if @ghhost
129
149
 
130
- org = File.basename(File.dirname(repo))
150
+ org = extract_org(repo)
151
+ SugarJar::Log.debug("Comparing org #{org} to ghuser #{@ghuser}")
131
152
  if org == @ghuser
132
- put 'Cloned forked or self-owned repo. Not creating "upstream".'
153
+ puts 'Cloned forked or self-owned repo. Not creating "upstream".'
133
154
  return
134
155
  end
135
156
 
@@ -137,9 +158,10 @@ class SugarJar
137
158
  if s.error?
138
159
  # if the fork command failed, we already have one, so we have
139
160
  # to swap the remote names ourselves
161
+ # newer 'hub's don't fail and do the right thing...
140
162
  SugarJar::Log.info("Fork (#{@ghuser}/#{reponame}) detected.")
141
163
  hub('remote', 'rename', 'origin', 'upstream')
142
- hub('remote', 'add', 'origin', repo.gsub("#{org}/", "#{@ghuser}/"))
164
+ hub('remote', 'add', 'origin', forked_path(repo, @ghuser))
143
165
  else
144
166
  SugarJar::Log.info("Forked #{reponame} to #{@ghuser}")
145
167
  end
@@ -150,38 +172,25 @@ class SugarJar
150
172
  alias sclone smartclone
151
173
 
152
174
  def lint
175
+ assert_in_repo
153
176
  exit(1) unless run_check('lint')
154
177
  end
155
178
 
156
179
  def unit
157
- exit(1) unless run_check('lint')
180
+ assert_in_repo
181
+ exit(1) unless run_check('unit')
158
182
  end
159
183
 
160
184
  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
185
+ assert_in_repo
186
+ _smartpush(remote, branch, false)
171
187
  end
172
188
 
173
189
  alias spush smartpush
174
190
 
175
191
  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
192
+ assert_in_repo
193
+ _smartpush(remote, branch, true)
185
194
  end
186
195
 
187
196
  alias fpush forcepush
@@ -191,8 +200,86 @@ class SugarJar
191
200
  puts hub('version').stdout
192
201
  end
193
202
 
203
+ def smartpullrequest
204
+ assert_in_repo
205
+ if dirty?
206
+ SugarJar::Log.warn(
207
+ 'Your repo is dirty, so I am not going to create a pull request. ' +
208
+ 'You should commit or amend and push it to your remote first.',
209
+ )
210
+ exit(1)
211
+ end
212
+ system(which('hub'), 'pull-request')
213
+ end
214
+
215
+ alias spr smartpullrequest
216
+ alias smartpr smartpullrequest
217
+
194
218
  private
195
219
 
220
+ def _smartpush(remote, branch, force)
221
+ unless remote && branch
222
+ remote ||= 'origin'
223
+ branch ||= current_branch
224
+ end
225
+
226
+ if dirty?
227
+ if @ignore_dirty
228
+ SugarJar::Log.warn(
229
+ 'Your repo is dirty, but --ignore-dirty was specified, so ' +
230
+ 'carrying on anyway.',
231
+ )
232
+ else
233
+ SugarJar::Log.error(
234
+ 'Your repo is dirty, so I am not going to push. Please commit ' +
235
+ 'or amend first.',
236
+ )
237
+ exit(1)
238
+ end
239
+ end
240
+
241
+ unless run_prepush
242
+ if @ignore_prerun_failure
243
+ SugarJar::Log.warn(
244
+ 'Pre-push checks failed, but --ignore-prerun-failure was ' +
245
+ 'specified, so carrying on anyway',
246
+ )
247
+ else
248
+ SugarJar::Log.error('Pre-push checks failed. Not pushing.')
249
+ exit(1)
250
+ end
251
+ end
252
+
253
+ args = ['push', remote, branch]
254
+ args << '--force-with-lease' if force
255
+ puts hub(*args).stderr
256
+ end
257
+
258
+ def dirty?
259
+ s = hub_nofail('diff', '--quiet')
260
+ s.error?
261
+ end
262
+
263
+ def extract_org(path)
264
+ if path.start_with?('http')
265
+ File.basename(File.dirname(path))
266
+ elsif path.start_with?('git@')
267
+ path.split(':')[1].split('/')[0]
268
+ else
269
+ # assume they passed in a hub-friendly name
270
+ path.split('/').first
271
+ end
272
+ end
273
+
274
+ def forked_path(path, username)
275
+ repo = if path.start_with?('http', 'git@')
276
+ File.basename(path)
277
+ else
278
+ "#{File.basename(path)}.git"
279
+ end
280
+ "git@github.com:#{username}/#{repo}"
281
+ end
282
+
196
283
  def set_hub_host
197
284
  return unless in_repo
198
285
 
@@ -201,7 +288,7 @@ class SugarJar
201
288
  SugarJar::Log.info("Setting repo hub.host = #{@ghhost}")
202
289
  else
203
290
  current = s.stdout
204
- if current == @ghost
291
+ if current == @ghhost
205
292
  SugarJar::Log.debug('Repo hub.host already set correctly')
206
293
  else
207
294
  # Even though we have an explicit config, in most cases, it
@@ -217,6 +304,47 @@ class SugarJar
217
304
  hub('config', '--local', '--add', 'hub.host', @ghhost)
218
305
  end
219
306
 
307
+ def set_commit_template
308
+ unless in_repo
309
+ SugarJar::Log.debug('Skipping set_commit_template: not in repo')
310
+ return
311
+ end
312
+
313
+ realpath = if @repo_config['commit_template'].start_with?('/')
314
+ @repo_config['commit_template']
315
+ else
316
+ "#{repo_root}/#{@repo_config['commit_template']}"
317
+ end
318
+ unless File.exist?(realpath)
319
+ die(
320
+ "Repo config specifies #{@repo_config['commit_template']} as the " +
321
+ 'commit template, but that file does not exist.',
322
+ )
323
+ end
324
+
325
+ s = hub_nofail('config', '--local', 'commit.template')
326
+ unless s.error?
327
+ current = s.stdout.strip
328
+ if current == @repo_config['commit_template']
329
+ SugarJar::Log.debug('Commit template already set correctly')
330
+ return
331
+ else
332
+ SugarJar::Log.warn(
333
+ "Updating repo-specific commit template from #{current} " +
334
+ "to #{@repo_config['commit_template']}",
335
+ )
336
+ end
337
+ end
338
+
339
+ SugarJar::Log.debug(
340
+ 'Setting repo-specific commit template to ' +
341
+ "#{@repo_config['commit_template']} per sugarjar repo config.",
342
+ )
343
+ hub(
344
+ 'config', '--local', 'commit.template', @repo_config['commit_template']
345
+ )
346
+ end
347
+
220
348
  def run_check(type)
221
349
  unless @repo_config[type]
222
350
  SugarJar::Log.debug("No #{type} configured. Returning success")
@@ -231,16 +359,20 @@ class SugarJar
231
359
  return false
232
360
  end
233
361
  s = Mixlib::ShellOut.new(check).run_command
234
- if s.error?
235
- SugarJar::Log.info("#{type} #{check} failed\n#{s.stdout}")
236
- return false
237
- end
362
+ next unless s.error?
363
+
364
+ SugarJar::Log.info(
365
+ "#{type} #{check} failed, output follows (use debug for more)\n" +
366
+ s.stdout.to_s,
367
+ )
368
+ SugarJar::Log.debug(s.format_for_exception)
369
+ return false
238
370
  end
239
371
  end
240
372
  end
241
373
 
242
374
  def run_prepush
243
- @repo_config['on_push'].each do |item|
375
+ @repo_config['on_push']&.each do |item|
244
376
  SugarJar::Log.debug("Running on_push check type #{item}")
245
377
  unless send(:run_check, item)
246
378
  SugarJar::Log.info("Push check #{item} failed.")
@@ -353,8 +485,18 @@ class SugarJar
353
485
  def gitup
354
486
  SugarJar::Log.debug('Fetching upstream')
355
487
  fetch_upstream
488
+ curr = current_branch
356
489
  SugarJar::Log.debug('Rebasing')
357
490
  base = tracked_branch
491
+ if curr != 'master' && base == "origin/#{curr}"
492
+ SugarJar::Log.warn(
493
+ "This branch is tracking origin/#{curr}, which is probably your " +
494
+ 'downstream (where you push _to_) as opposed to your upstream ' +
495
+ '(where you pull _from_). This means that "sj up" is probably ' +
496
+ 'rebasing on the wrong thing and doing nothing. You probably want ' +
497
+ 'to do a "git branch -u upstream".',
498
+ )
499
+ end
358
500
  s = hub_nofail('rebase', base)
359
501
  s.error? ? nil : base
360
502
  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
 
@@ -28,7 +28,49 @@ class SugarJar
28
28
 
29
29
  def hub_nofail(*args)
30
30
  SugarJar::Log.trace("Running: hub #{args.join(' ')}")
31
- Mixlib::ShellOut.new([which('hub')] + args).run_command
31
+ s = Mixlib::ShellOut.new([which('hub')] + args).run_command
32
+ if s.error?
33
+ # depending on hub version and possibly other things, STDERR
34
+ # is either "Requires authentication" or "Must authenticate"
35
+ case s.stderr
36
+ when /^(Must|Requires) authenticat/
37
+ SugarJar::Log.info(
38
+ 'Hub was run but no github token exists. Will run "hub api user" ' +
39
+ "to force\nhub to authenticate...",
40
+ )
41
+ unless system(which('hub'), 'api', 'user')
42
+ SugarJar::Log.fatal(
43
+ 'That failed, I will bail out. Hub needs to get a github ' +
44
+ 'token. Try running "hub api user" (will list info about ' +
45
+ 'your account) and try this again when that works.',
46
+ )
47
+ exit(1)
48
+ end
49
+ SugarJar::Log.info('Re-running original hub command...')
50
+ s = Mixlib::ShellOut.new([which('hub')] + args).run_command
51
+ when /^fatal: could not read Username/
52
+ # On http(s) URLs, git may prompt for username/passwd
53
+ SugarJar::Log.info(
54
+ 'Hub was run but git prompted for authentication. This probably ' +
55
+ "means you have\nused an http repo URL instead of an ssh one. It " +
56
+ "is recommended you reclone\nusing 'sj sclone' to setup your " +
57
+ "remotes properly. However, in the meantime,\nwe'll go ahead " +
58
+ "and re-run the command in a shell so you can type in the\n" +
59
+ 'credentials.',
60
+ )
61
+ unless system(which('hub'), *args)
62
+ SugarJar::Log.fatal(
63
+ 'That failed, I will bail out. You can either manually change ' +
64
+ 'your remotes, or simply create a fresh clone with ' +
65
+ '"sj smartclone".',
66
+ )
67
+ exit(1)
68
+ end
69
+ SugarJar::Log.info('Re-running original hub command...')
70
+ s = Mixlib::ShellOut.new([which('hub')] + args).run_command
71
+ end
72
+ end
73
+ s
32
74
  end
33
75
 
34
76
  def hub(*args)
@@ -1,3 +1,3 @@
1
1
  class SugarJar
2
- VERSION = '0.0.2'.freeze
2
+ VERSION = '0.0.7'.freeze
3
3
  end
@@ -0,0 +1,22 @@
1
+ require_relative 'lib/sugarjar/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'sugarjar'
5
+ spec.version = SugarJar::VERSION
6
+ spec.summary = 'A git/github helper script'
7
+ spec.authors = ['Phil Dibowitz']
8
+ spec.email = ['phil@ipom.com']
9
+ spec.license = 'Apache-2.0'
10
+ spec.homepage = 'https://github.com/jaymzh/sugarjar'
11
+ spec.required_ruby_version = '>= 2.6.0'
12
+ docs = %w{README.md LICENSE Gemfile sugarjar.gemspec}
13
+ spec.extra_rdoc_files = docs
14
+ spec.executables << 'sj'
15
+ spec.files =
16
+ Dir.glob('lib/sugarjar/*.rb') +
17
+ Dir.glob('bin/*') +
18
+ docs
19
+
20
+ spec.add_dependency 'mixlib-log'
21
+ spec.add_dependency 'mixlib-shellout'
22
+ 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.2
4
+ version: 0.0.7
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-07 00:00:00.000000000 Z
11
+ date: 2020-11-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mixlib-log
@@ -38,65 +38,29 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: bundler
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: mdl
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: rubocop
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
41
  description:
84
42
  email:
85
43
  - phil@ipom.com
86
- executables: []
44
+ executables:
45
+ - sj
87
46
  extensions: []
88
47
  extra_rdoc_files:
89
48
  - README.md
90
49
  - LICENSE
50
+ - Gemfile
51
+ - sugarjar.gemspec
91
52
  files:
53
+ - Gemfile
92
54
  - LICENSE
93
55
  - README.md
56
+ - bin/sj
94
57
  - lib/sugarjar/commands.rb
95
58
  - lib/sugarjar/config.rb
96
59
  - lib/sugarjar/log.rb
97
60
  - lib/sugarjar/repoconfig.rb
98
61
  - lib/sugarjar/util.rb
99
62
  - lib/sugarjar/version.rb
63
+ - sugarjar.gemspec
100
64
  homepage: https://github.com/jaymzh/sugarjar
101
65
  licenses:
102
66
  - Apache-2.0
@@ -109,15 +73,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
109
73
  requirements:
110
74
  - - ">="
111
75
  - !ruby/object:Gem::Version
112
- version: '0'
76
+ version: 2.6.0
113
77
  required_rubygems_version: !ruby/object:Gem::Requirement
114
78
  requirements:
115
79
  - - ">="
116
80
  - !ruby/object:Gem::Version
117
81
  version: '0'
118
82
  requirements: []
119
- rubyforge_project:
120
- rubygems_version: 2.7.6.2
83
+ rubygems_version: 3.1.2
121
84
  signing_key:
122
85
  specification_version: 4
123
86
  summary: A git/github helper script