sugarjar 0.0.3 → 0.0.8

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: 2abc9bd19f20ab7da08bb9617490eeeec7019367ecc869309122929005ddf40f
4
- data.tar.gz: 010b62db181cf3b8e0c26cec49c0342107a2b0e18c151e66000a1c119332d7bd
3
+ metadata.gz: 56cbf17ade9b12f891a658d7224a4571890da6517d9ea77a6f3688b4ff593261
4
+ data.tar.gz: c0c51ce8312f592cc8fd0fe273c563c2c7d3b2e033776ba64d267767755bd720
5
5
  SHA512:
6
- metadata.gz: b5ba7da0c8d3acacfe3f02dd479c77ab721fc3bc31cd70d0845ae3d0d9efe849de765d1d652ef3b70aa4f8643014837ab9d6361ad8231c7713e8ef10c07a3744
7
- data.tar.gz: 6f34096847e760ab6cc346121bf3ee18b7b8490e458fefd9a68692b81e71ae0c8b3d2d84b39f2549efb18acbe033908f0dcf002b58ab2d6907455c9f102b125f
6
+ metadata.gz: 3d49efb6124a04112ab8bd80b145a2459bdeb56f79c468524e0c89bbc9ef183dfce001fe199b8274562083b338aa2d81073e818676cd6fb9a43250c7be80c736
7
+ data.tar.gz: 0acc7fb90fed326a8976a1f029e3f5a9720259c855df686e8fb52aa70f8dc009f500f1bb36ef1c5957b7291b5d36cd3e4e59e902582fc23ed66409a58f73869a
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,18 +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)
6
+ [![Gem Version](https://badge.fury.io/rb/sugarjar.svg)](https://badge.fury.io/rb/sugarjar)
4
7
 
5
8
  Welcome to SugarJar - a git/github helper. It leverages the amazing GitHub cli,
6
9
  [hub](https://hub.github.com/), so you'll need that installed.
7
10
 
8
11
  SugarJar is inspired by [arcanist](https://github.com/phacility/arcanist), and
9
- 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
10
13
  the Phabricator workflow this aims to bring to the GitHub workflow.
11
14
 
12
15
  In particular there are a lot of helpers for using a squash-merge workflow that
13
16
  is poorly handled by the standard toolsets.
14
17
 
15
- 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!
16
19
 
17
20
  If you don't, there's a ton of useful stuff for everyone!
18
21
 
@@ -20,12 +23,12 @@ If you don't, there's a ton of useful stuff for everyone!
20
23
 
21
24
  It is common for a PR to go back and forth with a variety of nits, lint fixes,
22
25
  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>`
26
+ when they accept a pull request. However, that means `git branch -d <branch>`
24
27
  doesn't work. Git will tell you the branch isn't fully merged. You can, of
25
28
  course `git branch -D <branch>`, but that does no safety checks at all, it
26
29
  forces the deletion.
27
30
 
28
- 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
29
32
  and safely deletes if so.
30
33
 
31
34
  ``` shell
@@ -73,7 +76,7 @@ This will:
73
76
  Note that it takes `hub`s short-names for repos. No need to specify a full URL,
74
77
  just a $org/$repo.
75
78
 
76
- 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
77
80
  destination directory to clone to. It will also pass any other unknown options
78
81
  to `git clone` under the hood.
79
82
 
@@ -117,7 +120,7 @@ small lint issue? Not anymore! SJ can be configured to run things before
117
120
  pushing. For example,in the SugarJar repo, we have it run Rubocop (ruby lint)
118
121
  and Markdownlint "on_push". If those fail, it lets you know and doesn't push.
119
122
 
120
- 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
121
124
  a given repo and if one or both should be run prior to pushing.
122
125
 
123
126
  The details on the config file format is below, but we provide three commands:
@@ -144,7 +147,7 @@ push if any of them fail.
144
147
  ## Better push defaults
145
148
 
146
149
  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
150
+ defaults for push. So if you `sj spush` with no arguments, it uses the
148
151
  `origin` remote and the same branch name you're on as the remote branch.
149
152
 
150
153
  ## Cleaning up your own history
@@ -155,11 +158,11 @@ combination of rebases, amends and force pushes. We provide two commands here
155
158
  to help.
156
159
 
157
160
  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
161
+ amend`. It will amend whatever you want to the most recent commit (just an
159
162
  alias for `git commit --amend`). It has a partner `qamend` (or `amendq` if you
160
163
  prefer) that will do so without prompting to update your commit message.
161
164
 
162
- 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
163
166
  --force`, but everyone knows that's incredibly dangerous. Is there a better
164
167
  way? There is! Git provides `git push --force-with-lease` - it checks to make
165
168
  sure you're up-to-date with the remote before forcing the push. But man that
@@ -173,7 +176,7 @@ When you want to start a new feature, you want to start developing against
173
176
  latest. That's why `sj feature` defaults to creating a branch against what we
174
177
  call "most master". That is, `upstream/master` if it exists, otherwise
175
178
  `origin/master` if that exists, otherwise `master`. You can pass in an
176
- additional arguement to base it off of something else.
179
+ additional argument to base it off of something else.
177
180
 
178
181
  ```shell
179
182
  $ git branch
@@ -245,17 +248,19 @@ troubleshoot configuration parsing.
245
248
  ## Repository Configuration
246
249
 
247
250
  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:
251
+ how to handle repo-specific things. Currently there options are:
250
252
 
251
- * lint - A list of scripts to run on `sj lint`. These should be linters like
253
+ * `lint` - A list of scripts to run on `sj lint`. These should be linters like
252
254
  rubocop or pyflake.
253
- * unit - A list of scripts to run on `sj unit`. These should be unittest
255
+ * `unit` - A list of scripts to run on `sj unit`. These should be unittest
254
256
  runners like rspec or pyunit.
255
- * on_push - A list of types (`lint`, `unit`) of checks to run before pushing.
257
+ * `on_push` - A list of types (`lint`, `unit`) of checks to run before pushing.
256
258
  It is highly recommended this is only `lint`. The goal here is to allow for
257
259
  the user to get quick stylistic feedback before pushing their branch to avoid
258
260
  the push-fix-push-fix loop.
261
+ * `commit_template` - A path to a commit template to set in the `commit.template`
262
+ git config for this repo. Should be either a fully-qualified path, or a path
263
+ relative to the repo root.
259
264
 
260
265
  Example configuration:
261
266
 
@@ -266,8 +271,17 @@ unit:
266
271
  - scripts/unit
267
272
  on_push:
268
273
  - lint
274
+ commit_template: .commit-template.txt
269
275
  ```
270
276
 
277
+ ### Commit Templates
278
+
279
+ While GitHub provides a way to specify a pull-request template by putting the
280
+ right file into a repo, there is no way to tell git to automatically pick up a
281
+ commit template by dropping a file in the repo. Users must do something like:
282
+ `git config commit.template <file>`. Making each developer do this is error
283
+ prone, so this setting will automatically set this up for each developer.
284
+
271
285
  ## Enterprise GitHub
272
286
 
273
287
  Like `hub`, SugarJar supports GitHub Enterprise. In fact, we provide extra
data/bin/sj ADDED
@@ -0,0 +1,266 @@
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 = { 'color' => true }
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
+ '--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
+
70
+ opts.on(
71
+ '--log-level LEVEL',
72
+ 'Set logging level (fatal, error, warning, info, debug, trace). Default: ' +
73
+ 'info',
74
+ ) do |level|
75
+ options['log_level'] = level
76
+ end
77
+
78
+ opts.on('--[no-]use-color', 'Enable color. [default: true]') do |color|
79
+ options['color'] = color
80
+ end
81
+
82
+ opts.on('--version') do
83
+ puts SugarJar::VERSION
84
+ exit
85
+ end
86
+
87
+ # rubocop:disable Layout/HeredocIndentation
88
+ opts.separator <<COMMANDS
89
+
90
+ COMMANDS:
91
+ amend
92
+ Amend the current commit. Alias for "git commit --amend".
93
+ Accepts other arguments such as "-a" or files.
94
+
95
+ amendq, qamend
96
+ Same as "amend" but without changing the message. Alias for
97
+ "git commit --amend --no-edit".
98
+
99
+ bclean
100
+ If safe, delete the current branch. Unlike "git branch -d",
101
+ bclean can handle squash-merged branches. Think of it as
102
+ a smarter "git branch -d".
103
+
104
+ bcleanall
105
+ Walk all branches, and try to delete them if it's safe. See
106
+ "bclean" for details.
107
+
108
+ binfo
109
+ Verbose information about the current branch.
110
+
111
+ br
112
+ Verbose branch list. An alias for "git branch -v".
113
+
114
+ feature
115
+ Create a "feature" branch. It's morally equivalent to
116
+ "git checkout -b" except it defaults to creating it based on
117
+ some form of 'master' instead of your current branch. In order
118
+ of preference it will be upstream/master, origin/master, master,
119
+ depending upon what remotes are available.
120
+
121
+ forcepush, fpush
122
+ The same as "smartpush", but uses "--force-with-lease". This is
123
+ a "safer" way of doing force-pushes and is the recommended way
124
+ to push after rebasing or amending. Never do this to shared
125
+ branches. Very convenient for keeping the branch behind a pull-
126
+ request clean.
127
+
128
+ lint
129
+ Run any linters configured in .sugarjar.yaml.
130
+
131
+ smartclone, sclone
132
+ A smart wrapper to "git clone" that handles forking and managing
133
+ remotes for you.
134
+ It will clone a git repository using hub-style short name
135
+ ("$org/$repo"). If the org of the repository is not the same
136
+ as your github-user then it will fork the repo for you to
137
+ your account (if not already done) and then setup your remotes
138
+ so that "origin" is your fork and "upstream" is the upstream.
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
+
149
+ smartpush, spush
150
+ A smart wrapper to "git push" that runs whatever is defined in
151
+ "on_push" in .sugarjar.yml, and only pushes if they succeed.
152
+
153
+ unit
154
+ Run any unitests configured in .sugarjar.yaml.
155
+
156
+ up
157
+ Rebase the current branch on upstream/master or origin/master.
158
+
159
+ upall
160
+ Same as "up", but for all branches.
161
+
162
+ version
163
+ Print the version of sugarjar, and then run 'hub version'
164
+ to show the hub and git versions.
165
+ COMMANDS
166
+
167
+ # rubocop:enable Layout/HeredocIndentation
168
+ end
169
+
170
+ # we make a copy of these because we will assign back to the ARGV
171
+ # we parse later. We also need a pristine copy in case we want to
172
+ # run git as we were called.
173
+ argv_copy = ARGV.dup
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 }))
178
+ extra_opts = []
179
+
180
+ # as with above, this can't go into 'options', until after we parse
181
+ # the command line args
182
+ config = SugarJar::Config.config
183
+
184
+ valid_commands = sj.public_methods - Object.public_methods
185
+
186
+ is_valid_command = ARGV.any? { |arg| valid_commands.include?(arg.to_s.to_sym) }
187
+
188
+ # if we're configured to fall thru and the subcommand isn't one
189
+ # we recognize, don't parse the options as they may be different
190
+ # than git's. For example `git config -l` - we error because we
191
+ # require an arguement to `-l`.
192
+ if config['fallthru'] && !is_valid_command
193
+ SugarJar::Log.debug(
194
+ 'Skipping option parsing: fall-thru is set and we do not recognize ' +
195
+ 'any subcommands',
196
+ )
197
+ else
198
+ # We want to allow people to pass in extra args to be passed to
199
+ # git commands, but OptionParser doesn't easily allow this. So we
200
+ # loop over it, catching exceptions.
201
+ begin
202
+ # HOWEVER, anytime it throws an exception, for some reason, it clears
203
+ # out all of ARGV, or whatever you passed to as ARGV.
204
+ #
205
+ # This not only prevents further parsing, but also means we lose
206
+ # any non-option arguements (like the subcommand!)
207
+ #
208
+ # So we save a copy, and if we throw an exception, save the option that
209
+ # caused it, remove that option from our copy, and then re-populate argv
210
+ # with what's left.
211
+ #
212
+ # By doing this we not only get to parse all the options properly and
213
+ # save unknown ones, but non-option arguements, which OptionParser
214
+ # normally leaves in ARGV stay in ARGV.
215
+ saved_argv = argv_copy.dup
216
+ parser.parse!(argv_copy)
217
+ rescue OptionParser::InvalidOption => e
218
+ SugarJar::Log.debug("Saving unknown argument #{e.args}")
219
+ extra_opts += e.args
220
+
221
+ # e.args is an array, but it's only ever one arguement per exception
222
+ saved_argv.delete(e.args.first)
223
+ argv_copy = saved_argv.dup
224
+ SugarJar::Log.debug(
225
+ "Continuing option parsing with remaining ARGV: #{argv_copy}",
226
+ )
227
+ retry
228
+ end
229
+ end
230
+
231
+ if ARGV.empty?
232
+ puts parser
233
+ exit
234
+ end
235
+
236
+ options = config.merge(options)
237
+
238
+ # Recreate SJ with all of our options
239
+ SugarJar::Log.level = options['log_level'].to_sym if options['log_level']
240
+ sj = SugarJar::Commands.new(options)
241
+
242
+ subcommand = argv_copy.reject { |x| x.start_with?('-') }.first
243
+ argv_copy.delete(subcommand)
244
+ SugarJar::Log.debug("subcommand is #{subcommand}")
245
+
246
+ # Extra options we got, plus any left over arguements are what we
247
+ # pass to Commands so they can be passed to git as necessary
248
+ extra_opts += argv_copy
249
+ SugarJar::Log.debug("extra unknown options: #{extra_opts}")
250
+
251
+ if subcommand == 'help'
252
+ puts parser
253
+ exit
254
+ end
255
+
256
+ if is_valid_command
257
+ SugarJar::Log.debug(
258
+ "running #{subcommand}; extra opts: #{extra_opts.join(', ')}",
259
+ )
260
+ sj.send(subcommand.to_sym, *extra_opts)
261
+ elsif options['fallthru']
262
+ SugarJar::Log.debug("Falling thru to: hub #{ARGV.join(' ')}")
263
+ exec('hub', *ARGV)
264
+ else
265
+ SugarJar::Log.error("No such subcommand: #{subcommand}")
266
+ 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,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
@@ -127,9 +171,10 @@ class SugarJar
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_path(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,86 @@ 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(path)
288
+ if path.start_with?('http')
289
+ File.basename(File.dirname(path))
290
+ elsif path.start_with?('git@')
291
+ path.split(':')[1].split('/')[0]
292
+ else
293
+ # assume they passed in a hub-friendly name
294
+ path.split('/').first
295
+ end
296
+ end
297
+
298
+ def forked_path(path, username)
299
+ repo = if path.start_with?('http', 'git@')
300
+ File.basename(path)
301
+ else
302
+ "#{File.basename(path)}.git"
303
+ end
304
+ "git@github.com:#{username}/#{repo}"
305
+ end
306
+
196
307
  def set_hub_host
197
308
  return unless in_repo
198
309
 
@@ -201,7 +312,7 @@ class SugarJar
201
312
  SugarJar::Log.info("Setting repo hub.host = #{@ghhost}")
202
313
  else
203
314
  current = s.stdout
204
- if current == @ghost
315
+ if current == @ghhost
205
316
  SugarJar::Log.debug('Repo hub.host already set correctly')
206
317
  else
207
318
  # Even though we have an explicit config, in most cases, it
@@ -217,6 +328,47 @@ class SugarJar
217
328
  hub('config', '--local', '--add', 'hub.host', @ghhost)
218
329
  end
219
330
 
331
+ def set_commit_template
332
+ unless in_repo
333
+ SugarJar::Log.debug('Skipping set_commit_template: not in repo')
334
+ return
335
+ end
336
+
337
+ realpath = if @repo_config['commit_template'].start_with?('/')
338
+ @repo_config['commit_template']
339
+ else
340
+ "#{repo_root}/#{@repo_config['commit_template']}"
341
+ end
342
+ unless File.exist?(realpath)
343
+ die(
344
+ "Repo config specifies #{@repo_config['commit_template']} as the " +
345
+ 'commit template, but that file does not exist.',
346
+ )
347
+ end
348
+
349
+ s = hub_nofail('config', '--local', 'commit.template')
350
+ unless s.error?
351
+ current = s.stdout.strip
352
+ if current == @repo_config['commit_template']
353
+ SugarJar::Log.debug('Commit template already set correctly')
354
+ return
355
+ else
356
+ SugarJar::Log.warn(
357
+ "Updating repo-specific commit template from #{current} " +
358
+ "to #{@repo_config['commit_template']}",
359
+ )
360
+ end
361
+ end
362
+
363
+ SugarJar::Log.debug(
364
+ 'Setting repo-specific commit template to ' +
365
+ "#{@repo_config['commit_template']} per sugarjar repo config.",
366
+ )
367
+ hub(
368
+ 'config', '--local', 'commit.template', @repo_config['commit_template']
369
+ )
370
+ end
371
+
220
372
  def run_check(type)
221
373
  unless @repo_config[type]
222
374
  SugarJar::Log.debug("No #{type} configured. Returning success")
@@ -224,17 +376,26 @@ class SugarJar
224
376
  end
225
377
  Dir.chdir repo_root do
226
378
  @repo_config[type].each do |check|
227
- SugarJar::Log.info("Running #{type} #{check}")
379
+ SugarJar::Log.debug("Running #{type} #{check}")
228
380
 
229
- unless File.exist?(check)
381
+ unless File.exist?(check.split.first)
230
382
  SugarJar::Log.error("Configured #{type} #{check} does not exist!")
231
383
  return false
232
384
  end
233
385
  s = Mixlib::ShellOut.new(check).run_command
234
- if s.error?
235
- SugarJar::Log.info("#{type} #{check} failed\n#{s.stdout}")
236
- return false
386
+ unless s.error?
387
+ SugarJar::Log.info(
388
+ "[#{type}] #{check}: #{color('OK', :green)}",
389
+ )
390
+ next
237
391
  end
392
+
393
+ SugarJar::Log.info(
394
+ "[#{type}] #{check} #{color('failed', :red)}, output follows " +
395
+ "(see debug for more)\n#{s.stdout}",
396
+ )
397
+ SugarJar::Log.debug(s.format_for_exception)
398
+ return false
238
399
  end
239
400
  end
240
401
  end
@@ -243,7 +404,7 @@ class SugarJar
243
404
  @repo_config['on_push']&.each do |item|
244
405
  SugarJar::Log.debug("Running on_push check type #{item}")
245
406
  unless send(:run_check, item)
246
- SugarJar::Log.info("Push check #{item} failed.")
407
+ SugarJar::Log.info("[prepush]: #{item} #{color('failed', :red)}.")
247
408
  return false
248
409
  end
249
410
  end
@@ -269,7 +430,6 @@ class SugarJar
269
430
  hub('checkout', 'master')
270
431
  hub('branch', '-D', name)
271
432
  gitup
272
- SugarJar::Log.info("Reaped branch #{name}")
273
433
  true
274
434
  end
275
435
 
@@ -310,7 +470,7 @@ class SugarJar
310
470
  s = hub_nofail('merge', '--squash', branch)
311
471
  if s.error?
312
472
  cleanup_tmp_branch(tmpbranch, branch)
313
- SugarJar::Log.error(
473
+ SugarJar::Log.debug(
314
474
  'Failed to merge changes into current master. This means we could ' +
315
475
  'not figure out if this is merged or not. Check manually and use ' +
316
476
  "'git branch -D #{branch}' if it is safe to do so.",
@@ -353,8 +513,18 @@ class SugarJar
353
513
  def gitup
354
514
  SugarJar::Log.debug('Fetching upstream')
355
515
  fetch_upstream
516
+ curr = current_branch
356
517
  SugarJar::Log.debug('Rebasing')
357
518
  base = tracked_branch
519
+ if curr != 'master' && base == "origin/#{curr}"
520
+ SugarJar::Log.warn(
521
+ "This branch is tracking origin/#{curr}, which is probably your " +
522
+ 'downstream (where you push _to_) as opposed to your upstream ' +
523
+ '(where you pull _from_). This means that "sj up" is probably ' +
524
+ 'rebasing on the wrong thing and doing nothing. You probably want ' +
525
+ 'to do a "git branch -u upstream".',
526
+ )
527
+ end
358
528
  s = hub_nofail('rebase', base)
359
529
  s.error? ? nil : base
360
530
  end
@@ -399,5 +569,20 @@ class SugarJar
399
569
  end
400
570
  @remote
401
571
  end
572
+
573
+ def color(string, *colors)
574
+ if @color
575
+ pastel.decorate(string, *colors)
576
+ else
577
+ string
578
+ end
579
+ end
580
+
581
+ def pastel
582
+ @pastel ||= begin
583
+ require 'pastel'
584
+ Pastel.new
585
+ end
586
+ end
402
587
  end
403
588
  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
 
@@ -27,8 +27,54 @@ 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/
56
+ # On http(s) URLs, git may prompt for username/passwd
57
+ SugarJar::Log.info(
58
+ 'Hub was run but git prompted for authentication. This probably ' +
59
+ "means you have\nused an http repo URL instead of an ssh one. It " +
60
+ "is recommended you reclone\nusing 'sj sclone' to setup your " +
61
+ "remotes properly. However, in the meantime,\nwe'll go ahead " +
62
+ "and re-run the command in a shell so you can type in the\n" +
63
+ 'credentials.',
64
+ )
65
+ unless system(which('hub'), *args)
66
+ SugarJar::Log.fatal(
67
+ 'That failed, I will bail out. You can either manually change ' +
68
+ 'your remotes, or simply create a fresh clone with ' +
69
+ '"sj smartclone".',
70
+ )
71
+ exit(1)
72
+ end
73
+ SugarJar::Log.info('Re-running original hub command...')
74
+ s = Mixlib::ShellOut.new([which('hub')] + args).run_command
75
+ end
76
+ end
77
+ s
32
78
  end
33
79
 
34
80
  def hub(*args)
@@ -1,3 +1,3 @@
1
1
  class SugarJar
2
- VERSION = '0.0.3'.freeze
2
+ VERSION = '0.0.8'.freeze
3
3
  end
@@ -0,0 +1,23 @@
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
+ spec.add_dependency 'pastel'
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.3
4
+ version: 0.0.8
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-09 00:00:00.000000000 Z
11
+ date: 2020-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mixlib-log
@@ -39,41 +39,13 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: bundler
42
+ name: pastel
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
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
48
+ type: :runtime
77
49
  prerelease: false
78
50
  version_requirements: !ruby/object:Gem::Requirement
79
51
  requirements:
@@ -83,20 +55,26 @@ dependencies:
83
55
  description:
84
56
  email:
85
57
  - phil@ipom.com
86
- executables: []
58
+ executables:
59
+ - sj
87
60
  extensions: []
88
61
  extra_rdoc_files:
89
62
  - README.md
90
63
  - LICENSE
64
+ - Gemfile
65
+ - sugarjar.gemspec
91
66
  files:
67
+ - Gemfile
92
68
  - LICENSE
93
69
  - README.md
70
+ - bin/sj
94
71
  - lib/sugarjar/commands.rb
95
72
  - lib/sugarjar/config.rb
96
73
  - lib/sugarjar/log.rb
97
74
  - lib/sugarjar/repoconfig.rb
98
75
  - lib/sugarjar/util.rb
99
76
  - lib/sugarjar/version.rb
77
+ - sugarjar.gemspec
100
78
  homepage: https://github.com/jaymzh/sugarjar
101
79
  licenses:
102
80
  - Apache-2.0
@@ -109,15 +87,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
109
87
  requirements:
110
88
  - - ">="
111
89
  - !ruby/object:Gem::Version
112
- version: '0'
90
+ version: 2.6.0
113
91
  required_rubygems_version: !ruby/object:Gem::Requirement
114
92
  requirements:
115
93
  - - ">="
116
94
  - !ruby/object:Gem::Version
117
95
  version: '0'
118
96
  requirements: []
119
- rubyforge_project:
120
- rubygems_version: 2.7.6.2
97
+ rubygems_version: 3.1.2
121
98
  signing_key:
122
99
  specification_version: 4
123
100
  summary: A git/github helper script