sugarjar 0.0.2 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +10 -0
- data/README.md +26 -13
- data/bin/sj +257 -0
- data/lib/sugarjar/commands.rb +173 -31
- data/lib/sugarjar/config.rb +2 -2
- data/lib/sugarjar/util.rb +43 -1
- data/lib/sugarjar/version.rb +1 -1
- data/sugarjar.gemspec +22 -0
- metadata +11 -48
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9fefe2bc9c94d95d3c869f8636bbabf433ec6196ae8d68b7ac131b4a955a882b
|
4
|
+
data.tar.gz: 5237f94836f686a0e82a8a18543b54179e7190ce874d481e59ae5fe685aa26d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8ade9a12c3ba79a5ba591123179a80aeff6db7cbd4ab48c42cc27ecd5f5f4c3bcd713b99a04654b7c28e4fca715c3f6535f9938a97f3c55fc41169791f8d6a6
|
7
|
+
data.tar.gz: cbe9733235652a2ee5d777e53eed4a0f7deb9ffdcb69b0825b89c7831c52e5317e1e854b61e038d1a27fd92c85326e560c287e9e162d3068abcd246bc0e9ee48
|
data/Gemfile
ADDED
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# SugarJar
|
2
2
|
|
3
|
-
](https://github.com/jaymzh/sugarjar/actions?query=workflow%3ALint)
|
4
|
+
[](https://github.com/jaymzh/sugarjar/actions?query=workflow%3A%22DCO+Check%22)
|
5
|
+
[](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
|
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.
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
data/lib/sugarjar/commands.rb
CHANGED
@@ -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(
|
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 =
|
150
|
+
org = extract_org(repo)
|
151
|
+
SugarJar::Log.debug("Comparing org #{org} to ghuser #{@ghuser}")
|
131
152
|
if org == @ghuser
|
132
|
-
|
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
|
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
|
-
|
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
|
-
|
162
|
-
|
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
|
-
|
177
|
-
|
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 == @
|
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
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
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']
|
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
|
data/lib/sugarjar/config.rb
CHANGED
@@ -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
|
-
'
|
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
@@ -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)
|
data/lib/sugarjar/version.rb
CHANGED
data/sugarjar.gemspec
ADDED
@@ -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.
|
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-
|
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:
|
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
|
-
|
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
|