sugarjar 0.0.4 → 0.0.9
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 +1 -0
- data/README.md +36 -16
- data/bin/sj +39 -4
- data/lib/sugarjar/commands.rb +277 -51
- data/lib/sugarjar/config.rb +2 -2
- data/lib/sugarjar/util.rb +48 -1
- data/lib/sugarjar/version.rb +1 -1
- data/sugarjar.gemspec +2 -0
- metadata +18 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b12b221b467eca9aeba30396d059870a82608120ce1739ded50b4aef55ce7ba
|
4
|
+
data.tar.gz: c84bcab24de60fc61a0241f7c2940d6da6d78bc7416fa3fb92500528a447f9c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e041cd65eb939ea1d2153bf13a8a019e05f194fcf6ce4326c69d62a2de9e34e1d036cf759c5ea17b5b42d8e7e159fc99d65ee75470580a4ed15a854fffc7c87f
|
7
|
+
data.tar.gz: 2c47c2920b6a774ffb83ef16761183ac7dbfcc808a6885335bbeeee5470a29ceff343df4e3748897ab4e8e681e7c7d8b9a5fc3c7ed4d44fafaa40dda6c08b300
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,19 +1,21 @@
|
|
1
1
|
# SugarJar
|
2
2
|
|
3
|
-
](https://github.com/jaymzh/sugarjar/actions?query=workflow%3ALint)
|
4
|
+
[](https://github.com/jaymzh/sugarjar/actions?query=workflow%3AUnittests)
|
5
|
+
[](https://github.com/jaymzh/sugarjar/actions?query=workflow%3A%22DCO+Check%22)
|
4
6
|
[](https://badge.fury.io/rb/sugarjar)
|
5
7
|
|
6
8
|
Welcome to SugarJar - a git/github helper. It leverages the amazing GitHub cli,
|
7
9
|
[hub](https://hub.github.com/), so you'll need that installed.
|
8
10
|
|
9
11
|
SugarJar is inspired by [arcanist](https://github.com/phacility/arcanist), and
|
10
|
-
|
12
|
+
its replacement at Facebook, JellyFish. Many of the features they provide for
|
11
13
|
the Phabricator workflow this aims to bring to the GitHub workflow.
|
12
14
|
|
13
15
|
In particular there are a lot of helpers for using a squash-merge workflow that
|
14
16
|
is poorly handled by the standard toolsets.
|
15
17
|
|
16
|
-
If you miss Mondrian or
|
18
|
+
If you miss Mondrian or Phabricator - this is the tool for you!
|
17
19
|
|
18
20
|
If you don't, there's a ton of useful stuff for everyone!
|
19
21
|
|
@@ -21,12 +23,12 @@ If you don't, there's a ton of useful stuff for everyone!
|
|
21
23
|
|
22
24
|
It is common for a PR to go back and forth with a variety of nits, lint fixes,
|
23
25
|
typos, etc. that can muddy history. So many projects will "squash and merge"
|
24
|
-
when they accept a pull request.
|
26
|
+
when they accept a pull request. However, that means `git branch -d <branch>`
|
25
27
|
doesn't work. Git will tell you the branch isn't fully merged. You can, of
|
26
28
|
course `git branch -D <branch>`, but that does no safety checks at all, it
|
27
29
|
forces the deletion.
|
28
30
|
|
29
|
-
Enter `sj bclean` - it determines
|
31
|
+
Enter `sj bclean` - it determines if the contents of your branch has been merge
|
30
32
|
and safely deletes if so.
|
31
33
|
|
32
34
|
``` shell
|
@@ -74,7 +76,7 @@ This will:
|
|
74
76
|
Note that it takes `hub`s short-names for repos. No need to specify a full URL,
|
75
77
|
just a $org/$repo.
|
76
78
|
|
77
|
-
Like `git clone`, `sj sclone` will accept an additional
|
79
|
+
Like `git clone`, `sj sclone` will accept an additional argument as the
|
78
80
|
destination directory to clone to. It will also pass any other unknown options
|
79
81
|
to `git clone` under the hood.
|
80
82
|
|
@@ -118,7 +120,7 @@ small lint issue? Not anymore! SJ can be configured to run things before
|
|
118
120
|
pushing. For example,in the SugarJar repo, we have it run Rubocop (ruby lint)
|
119
121
|
and Markdownlint "on_push". If those fail, it lets you know and doesn't push.
|
120
122
|
|
121
|
-
You can configure SugarJar to tell
|
123
|
+
You can configure SugarJar to tell it how to run both lints and unittests for
|
122
124
|
a given repo and if one or both should be run prior to pushing.
|
123
125
|
|
124
126
|
The details on the config file format is below, but we provide three commands:
|
@@ -145,7 +147,7 @@ push if any of them fail.
|
|
145
147
|
## Better push defaults
|
146
148
|
|
147
149
|
In addition to running pre-push tests for you `smartpush` also picks smart
|
148
|
-
defaults for push. So if you `sj spush` with no
|
150
|
+
defaults for push. So if you `sj spush` with no arguments, it uses the
|
149
151
|
`origin` remote and the same branch name you're on as the remote branch.
|
150
152
|
|
151
153
|
## Cleaning up your own history
|
@@ -156,11 +158,11 @@ combination of rebases, amends and force pushes. We provide two commands here
|
|
156
158
|
to help.
|
157
159
|
|
158
160
|
The first is pretty straight forward and is basically just an alias: `sj
|
159
|
-
amend`. It will
|
161
|
+
amend`. It will amend whatever you want to the most recent commit (just an
|
160
162
|
alias for `git commit --amend`). It has a partner `qamend` (or `amendq` if you
|
161
163
|
prefer) that will do so without prompting to update your commit message.
|
162
164
|
|
163
|
-
So now you've rebased or amended, pushing becomes
|
165
|
+
So now you've rebased or amended, pushing becomes challenging. You can `git push
|
164
166
|
--force`, but everyone knows that's incredibly dangerous. Is there a better
|
165
167
|
way? There is! Git provides `git push --force-with-lease` - it checks to make
|
166
168
|
sure you're up-to-date with the remote before forcing the push. But man that
|
@@ -174,7 +176,7 @@ When you want to start a new feature, you want to start developing against
|
|
174
176
|
latest. That's why `sj feature` defaults to creating a branch against what we
|
175
177
|
call "most master". That is, `upstream/master` if it exists, otherwise
|
176
178
|
`origin/master` if that exists, otherwise `master`. You can pass in an
|
177
|
-
additional
|
179
|
+
additional argument to base it off of something else.
|
178
180
|
|
179
181
|
```shell
|
180
182
|
$ git branch
|
@@ -189,6 +191,13 @@ $ sj feature dependent-feature test-branch
|
|
189
191
|
Created feature branch dependent-feature based on test-branch
|
190
192
|
```
|
191
193
|
|
194
|
+
## Smartlog
|
195
|
+
|
196
|
+
Smartlog will show you a tree diagram of your branches! Simply run `sj
|
197
|
+
smartlog` or `sj sl` for short.
|
198
|
+
|
199
|
+

|
200
|
+
|
192
201
|
## And more!
|
193
202
|
|
194
203
|
See `sj help` for more commands!
|
@@ -246,17 +255,19 @@ troubleshoot configuration parsing.
|
|
246
255
|
## Repository Configuration
|
247
256
|
|
248
257
|
Sugarjar looks for a `.sugarjar.yaml` in the root of the repository to tell it
|
249
|
-
how to handle repo-specific things. Currently there are
|
250
|
-
configurations accepted:
|
258
|
+
how to handle repo-specific things. Currently there options are:
|
251
259
|
|
252
|
-
* lint - A list of scripts to run on `sj lint`. These should be linters like
|
260
|
+
* `lint` - A list of scripts to run on `sj lint`. These should be linters like
|
253
261
|
rubocop or pyflake.
|
254
|
-
* unit - A list of scripts to run on `sj unit`. These should be unittest
|
262
|
+
* `unit` - A list of scripts to run on `sj unit`. These should be unittest
|
255
263
|
runners like rspec or pyunit.
|
256
|
-
* on_push - A list of types (`lint`, `unit`) of checks to run before pushing.
|
264
|
+
* `on_push` - A list of types (`lint`, `unit`) of checks to run before pushing.
|
257
265
|
It is highly recommended this is only `lint`. The goal here is to allow for
|
258
266
|
the user to get quick stylistic feedback before pushing their branch to avoid
|
259
267
|
the push-fix-push-fix loop.
|
268
|
+
* `commit_template` - A path to a commit template to set in the `commit.template`
|
269
|
+
git config for this repo. Should be either a fully-qualified path, or a path
|
270
|
+
relative to the repo root.
|
260
271
|
|
261
272
|
Example configuration:
|
262
273
|
|
@@ -267,8 +278,17 @@ unit:
|
|
267
278
|
- scripts/unit
|
268
279
|
on_push:
|
269
280
|
- lint
|
281
|
+
commit_template: .commit-template.txt
|
270
282
|
```
|
271
283
|
|
284
|
+
### Commit Templates
|
285
|
+
|
286
|
+
While GitHub provides a way to specify a pull-request template by putting the
|
287
|
+
right file into a repo, there is no way to tell git to automatically pick up a
|
288
|
+
commit template by dropping a file in the repo. Users must do something like:
|
289
|
+
`git config commit.template <file>`. Making each developer do this is error
|
290
|
+
prone, so this setting will automatically set this up for each developer.
|
291
|
+
|
272
292
|
## Enterprise GitHub
|
273
293
|
|
274
294
|
Like `hub`, SugarJar supports GitHub Enterprise. In fact, we provide extra
|
data/bin/sj
CHANGED
@@ -13,7 +13,7 @@ SugarJar::Log.level = Logger::INFO
|
|
13
13
|
|
14
14
|
# Don't put defaults here, put them in SugarJar::Config - otherwise
|
15
15
|
# these defaults overwrite whatever is in config files.
|
16
|
-
options = {}
|
16
|
+
options = { 'color' => true }
|
17
17
|
# If ENV['SUGARJAR_DEBUG'] is set, it overrides the config file,
|
18
18
|
# but not the command line options, so set that one here. Also
|
19
19
|
# start the logger at that level, in case we are debugging option loading
|
@@ -53,6 +53,20 @@ parser = OptionParser.new do |opts|
|
|
53
53
|
exit
|
54
54
|
end
|
55
55
|
|
56
|
+
opts.on(
|
57
|
+
'--ignore-dirty',
|
58
|
+
'Tell command that check for a dirty repo to carry on anyway.',
|
59
|
+
) do
|
60
|
+
options['ignore_dirty'] = true
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.on(
|
64
|
+
'--ignore-prerun-failure',
|
65
|
+
'Ignore preprun failure on *push commands.',
|
66
|
+
) do
|
67
|
+
options['ignore_prerun_failure'] = true
|
68
|
+
end
|
69
|
+
|
56
70
|
opts.on(
|
57
71
|
'--log-level LEVEL',
|
58
72
|
'Set logging level (fatal, error, warning, info, debug, trace). Default: ' +
|
@@ -61,6 +75,10 @@ parser = OptionParser.new do |opts|
|
|
61
75
|
options['log_level'] = level
|
62
76
|
end
|
63
77
|
|
78
|
+
opts.on('--[no-]use-color', 'Enable color. [default: true]') do |color|
|
79
|
+
options['color'] = color
|
80
|
+
end
|
81
|
+
|
64
82
|
opts.on('--version') do
|
65
83
|
puts SugarJar::VERSION
|
66
84
|
exit
|
@@ -119,6 +137,15 @@ COMMANDS:
|
|
119
137
|
your account (if not already done) and then setup your remotes
|
120
138
|
so that "origin" is your fork and "upstream" is the upstream.
|
121
139
|
|
140
|
+
smartlog, sl
|
141
|
+
Inspired by Facebook's "sl" extension to Mercurial, this command
|
142
|
+
will show you a tree of all your local branches relative to your
|
143
|
+
upstream.
|
144
|
+
|
145
|
+
smartpullrequest, smartpr, spr
|
146
|
+
A smart wrapper to "hub pull-request" that checks if your repo
|
147
|
+
is dirty before creating the pull request.
|
148
|
+
|
122
149
|
smartpush, spush
|
123
150
|
A smart wrapper to "git push" that runs whatever is defined in
|
124
151
|
"on_push" in .sugarjar.yml, and only pushes if they succeed.
|
@@ -132,7 +159,7 @@ COMMANDS:
|
|
132
159
|
upall
|
133
160
|
Same as "up", but for all branches.
|
134
161
|
|
135
|
-
|
162
|
+
version
|
136
163
|
Print the version of sugarjar, and then run 'hub version'
|
137
164
|
to show the hub and git versions.
|
138
165
|
COMMANDS
|
@@ -144,7 +171,10 @@ end
|
|
144
171
|
# we parse later. We also need a pristine copy in case we want to
|
145
172
|
# run git as we were called.
|
146
173
|
argv_copy = ARGV.dup
|
147
|
-
|
174
|
+
|
175
|
+
# We don't have options yet, but we need an instance of SJ in order
|
176
|
+
# to list public methods. We will recreate it
|
177
|
+
sj = SugarJar::Commands.new(options.merge({ 'no_change' => true }))
|
148
178
|
extra_opts = []
|
149
179
|
|
150
180
|
# as with above, this can't go into 'options', until after we parse
|
@@ -204,7 +234,10 @@ if ARGV.empty?
|
|
204
234
|
end
|
205
235
|
|
206
236
|
options = config.merge(options)
|
237
|
+
|
238
|
+
# Recreate SJ with all of our options
|
207
239
|
SugarJar::Log.level = options['log_level'].to_sym if options['log_level']
|
240
|
+
sj = SugarJar::Commands.new(options)
|
208
241
|
|
209
242
|
subcommand = argv_copy.reject { |x| x.start_with?('-') }.first
|
210
243
|
argv_copy.delete(subcommand)
|
@@ -221,7 +254,9 @@ if subcommand == 'help'
|
|
221
254
|
end
|
222
255
|
|
223
256
|
if is_valid_command
|
224
|
-
SugarJar::Log.debug(
|
257
|
+
SugarJar::Log.debug(
|
258
|
+
"running #{subcommand}; extra opts: #{extra_opts.join(', ')}",
|
259
|
+
)
|
225
260
|
sj.send(subcommand.to_sym, *extra_opts)
|
226
261
|
elsif options['fallthru']
|
227
262
|
SugarJar::Log.debug("Falling thru to: hub #{ARGV.join(' ')}")
|
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,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(
|
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
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
60
|
+
if branch == 'master'
|
61
|
+
SugarJar::Log.debug('Skipping master')
|
62
|
+
next
|
63
|
+
end
|
45
64
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
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
|
-
|
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(
|
118
|
+
SugarJar::Log.info(
|
119
|
+
"#{color(current_branch, :green)} rebased on #{result}",
|
120
|
+
)
|
79
121
|
else
|
80
|
-
die(
|
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(
|
147
|
+
SugarJar::Log.info(
|
148
|
+
"#{color(branch, :green)} rebased on #{color(result, :green)}",
|
149
|
+
)
|
106
150
|
else
|
107
151
|
SugarJar::Log.error(
|
108
|
-
"
|
109
|
-
'branch',
|
152
|
+
"#{color(branch, :red)} failed rebase. Reverting attempt and " +
|
153
|
+
'moving to next branch',
|
110
154
|
)
|
111
155
|
hub('rebase', '--abort')
|
112
156
|
end
|
@@ -121,15 +165,16 @@ class SugarJar
|
|
121
165
|
reponame = File.basename(repo, '.git')
|
122
166
|
dir ||= reponame
|
123
167
|
SugarJar::Log.info("Cloning #{reponame}...")
|
124
|
-
hub('clone', repo, dir, *args)
|
168
|
+
hub('clone', canonicalize_repo(repo), dir, *args)
|
125
169
|
|
126
170
|
Dir.chdir dir do
|
127
171
|
# Now that we have a repo, if we have a hub host set it.
|
128
172
|
set_hub_host if @ghhost
|
129
173
|
|
130
|
-
org =
|
174
|
+
org = extract_org(repo)
|
175
|
+
SugarJar::Log.debug("Comparing org #{org} to ghuser #{@ghuser}")
|
131
176
|
if org == @ghuser
|
132
|
-
|
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
|
188
|
+
hub('remote', 'add', 'origin', forked_repo(repo, @ghuser))
|
143
189
|
else
|
144
190
|
SugarJar::Log.info("Forked #{reponame} to #{@ghuser}")
|
145
191
|
end
|
@@ -150,38 +196,25 @@ class SugarJar
|
|
150
196
|
alias sclone smartclone
|
151
197
|
|
152
198
|
def lint
|
199
|
+
assert_in_repo
|
153
200
|
exit(1) unless run_check('lint')
|
154
201
|
end
|
155
202
|
|
156
203
|
def unit
|
157
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
216
|
+
assert_in_repo
|
217
|
+
_smartpush(remote, branch, true)
|
185
218
|
end
|
186
219
|
|
187
220
|
alias fpush forcepush
|
@@ -191,8 +224,98 @@ class SugarJar
|
|
191
224
|
puts hub('version').stdout
|
192
225
|
end
|
193
226
|
|
227
|
+
def smartpullrequest
|
228
|
+
assert_in_repo
|
229
|
+
if dirty?
|
230
|
+
SugarJar::Log.warn(
|
231
|
+
'Your repo is dirty, so I am not going to create a pull request. ' +
|
232
|
+
'You should commit or amend and push it to your remote first.',
|
233
|
+
)
|
234
|
+
exit(1)
|
235
|
+
end
|
236
|
+
system(which('hub'), 'pull-request')
|
237
|
+
end
|
238
|
+
|
239
|
+
alias spr smartpullrequest
|
240
|
+
alias smartpr smartpullrequest
|
241
|
+
|
194
242
|
private
|
195
243
|
|
244
|
+
def _smartpush(remote, branch, force)
|
245
|
+
unless remote && branch
|
246
|
+
remote ||= 'origin'
|
247
|
+
branch ||= current_branch
|
248
|
+
end
|
249
|
+
|
250
|
+
if dirty?
|
251
|
+
if @ignore_dirty
|
252
|
+
SugarJar::Log.warn(
|
253
|
+
'Your repo is dirty, but --ignore-dirty was specified, so ' +
|
254
|
+
'carrying on anyway.',
|
255
|
+
)
|
256
|
+
else
|
257
|
+
SugarJar::Log.error(
|
258
|
+
'Your repo is dirty, so I am not going to push. Please commit ' +
|
259
|
+
'or amend first.',
|
260
|
+
)
|
261
|
+
exit(1)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
unless run_prepush
|
266
|
+
if @ignore_prerun_failure
|
267
|
+
SugarJar::Log.warn(
|
268
|
+
'Pre-push checks failed, but --ignore-prerun-failure was ' +
|
269
|
+
'specified, so carrying on anyway',
|
270
|
+
)
|
271
|
+
else
|
272
|
+
SugarJar::Log.error('Pre-push checks failed. Not pushing.')
|
273
|
+
exit(1)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
args = ['push', remote, branch]
|
278
|
+
args << '--force-with-lease' if force
|
279
|
+
puts hub(*args).stderr
|
280
|
+
end
|
281
|
+
|
282
|
+
def dirty?
|
283
|
+
s = hub_nofail('diff', '--quiet')
|
284
|
+
s.error?
|
285
|
+
end
|
286
|
+
|
287
|
+
def extract_org(repo)
|
288
|
+
if repo.start_with?('http')
|
289
|
+
File.basename(File.dirname(repo))
|
290
|
+
elsif repo.start_with?('git@')
|
291
|
+
repo.split(':')[1].split('/')[0]
|
292
|
+
else
|
293
|
+
# assume they passed in a hub-friendly name
|
294
|
+
repo.split('/').first
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
def forked_repo(repo, username)
|
299
|
+
repo = if repo.start_with?('http', 'git@')
|
300
|
+
File.basename(repo)
|
301
|
+
else
|
302
|
+
"#{File.basename(repo)}.git"
|
303
|
+
end
|
304
|
+
"git@#{@ghhost || 'github.com'}:#{username}/#{repo}"
|
305
|
+
end
|
306
|
+
|
307
|
+
# Hub will default to https, but we should always default to SSH
|
308
|
+
# unless otherwise specified since https will cause prompting.
|
309
|
+
def canonicalize_repo(repo)
|
310
|
+
# if they fully-qualified it, we're good
|
311
|
+
return repo if repo.start_with?('http', 'git@')
|
312
|
+
|
313
|
+
# otherwise, ti's a shortname
|
314
|
+
cr = "git@#{@ghhost || 'github.com'}:#{repo}.git"
|
315
|
+
SugarJar::Log.debug("canonicalized #{repo} to #{cr}")
|
316
|
+
cr
|
317
|
+
end
|
318
|
+
|
196
319
|
def set_hub_host
|
197
320
|
return unless in_repo
|
198
321
|
|
@@ -201,7 +324,7 @@ class SugarJar
|
|
201
324
|
SugarJar::Log.info("Setting repo hub.host = #{@ghhost}")
|
202
325
|
else
|
203
326
|
current = s.stdout
|
204
|
-
if current == @
|
327
|
+
if current == @ghhost
|
205
328
|
SugarJar::Log.debug('Repo hub.host already set correctly')
|
206
329
|
else
|
207
330
|
# Even though we have an explicit config, in most cases, it
|
@@ -217,6 +340,47 @@ class SugarJar
|
|
217
340
|
hub('config', '--local', '--add', 'hub.host', @ghhost)
|
218
341
|
end
|
219
342
|
|
343
|
+
def set_commit_template
|
344
|
+
unless in_repo
|
345
|
+
SugarJar::Log.debug('Skipping set_commit_template: not in repo')
|
346
|
+
return
|
347
|
+
end
|
348
|
+
|
349
|
+
realpath = if @repo_config['commit_template'].start_with?('/')
|
350
|
+
@repo_config['commit_template']
|
351
|
+
else
|
352
|
+
"#{repo_root}/#{@repo_config['commit_template']}"
|
353
|
+
end
|
354
|
+
unless File.exist?(realpath)
|
355
|
+
die(
|
356
|
+
"Repo config specifies #{@repo_config['commit_template']} as the " +
|
357
|
+
'commit template, but that file does not exist.',
|
358
|
+
)
|
359
|
+
end
|
360
|
+
|
361
|
+
s = hub_nofail('config', '--local', 'commit.template')
|
362
|
+
unless s.error?
|
363
|
+
current = s.stdout.strip
|
364
|
+
if current == @repo_config['commit_template']
|
365
|
+
SugarJar::Log.debug('Commit template already set correctly')
|
366
|
+
return
|
367
|
+
else
|
368
|
+
SugarJar::Log.warn(
|
369
|
+
"Updating repo-specific commit template from #{current} " +
|
370
|
+
"to #{@repo_config['commit_template']}",
|
371
|
+
)
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
SugarJar::Log.debug(
|
376
|
+
'Setting repo-specific commit template to ' +
|
377
|
+
"#{@repo_config['commit_template']} per sugarjar repo config.",
|
378
|
+
)
|
379
|
+
hub(
|
380
|
+
'config', '--local', 'commit.template', @repo_config['commit_template']
|
381
|
+
)
|
382
|
+
end
|
383
|
+
|
220
384
|
def run_check(type)
|
221
385
|
unless @repo_config[type]
|
222
386
|
SugarJar::Log.debug("No #{type} configured. Returning success")
|
@@ -224,17 +388,55 @@ class SugarJar
|
|
224
388
|
end
|
225
389
|
Dir.chdir repo_root do
|
226
390
|
@repo_config[type].each do |check|
|
227
|
-
SugarJar::Log.
|
391
|
+
SugarJar::Log.debug("Running #{type} #{check}")
|
228
392
|
|
229
|
-
|
230
|
-
|
393
|
+
short = check.split.first
|
394
|
+
unless File.exist?(short)
|
395
|
+
SugarJar::Log.error("Configured #{type} #{short} does not exist!")
|
231
396
|
return false
|
232
397
|
end
|
233
398
|
s = Mixlib::ShellOut.new(check).run_command
|
399
|
+
|
400
|
+
# Linters auto-correct, lets handle that gracefully
|
401
|
+
if type == 'lint' && dirty?
|
402
|
+
SugarJar::Log.info(
|
403
|
+
"[#{type}] #{short}: #{color('Corrected', :yellow)}",
|
404
|
+
)
|
405
|
+
SugarJar::Log.warn(
|
406
|
+
"The linter modified the repo. Here's the diff:\n",
|
407
|
+
)
|
408
|
+
puts hub('diff').stdout
|
409
|
+
loop do
|
410
|
+
$stdout.print(
|
411
|
+
"\nWould you like to\n\t[q]uit and inspect\n\t[a]mend the " +
|
412
|
+
"changes to the current commit and re-run\n > ",
|
413
|
+
)
|
414
|
+
ans = $stdin.gets.strip
|
415
|
+
case ans
|
416
|
+
when /^q/
|
417
|
+
SugarJar::Log.info('Exiting at user request.')
|
418
|
+
exit(1)
|
419
|
+
when /^a/
|
420
|
+
qamend('-a')
|
421
|
+
# break here, if we get out of this loop we 'redo', assuming
|
422
|
+
# the user chose this option
|
423
|
+
break
|
424
|
+
end
|
425
|
+
end
|
426
|
+
redo
|
427
|
+
end
|
428
|
+
|
234
429
|
if s.error?
|
235
|
-
SugarJar::Log.info(
|
430
|
+
SugarJar::Log.info(
|
431
|
+
"[#{type}] #{short} #{color('failed', :red)}, output follows " +
|
432
|
+
"(see debug for more)\n#{s.stdout}",
|
433
|
+
)
|
434
|
+
SugarJar::Log.debug(s.format_for_exception)
|
236
435
|
return false
|
237
436
|
end
|
437
|
+
SugarJar::Log.info(
|
438
|
+
"[#{type}] #{short}: #{color('OK', :green)}",
|
439
|
+
)
|
238
440
|
end
|
239
441
|
end
|
240
442
|
end
|
@@ -243,7 +445,7 @@ class SugarJar
|
|
243
445
|
@repo_config['on_push']&.each do |item|
|
244
446
|
SugarJar::Log.debug("Running on_push check type #{item}")
|
245
447
|
unless send(:run_check, item)
|
246
|
-
SugarJar::Log.info("
|
448
|
+
SugarJar::Log.info("[prepush]: #{item} #{color('failed', :red)}.")
|
247
449
|
return false
|
248
450
|
end
|
249
451
|
end
|
@@ -269,7 +471,6 @@ class SugarJar
|
|
269
471
|
hub('checkout', 'master')
|
270
472
|
hub('branch', '-D', name)
|
271
473
|
gitup
|
272
|
-
SugarJar::Log.info("Reaped branch #{name}")
|
273
474
|
true
|
274
475
|
end
|
275
476
|
|
@@ -310,7 +511,7 @@ class SugarJar
|
|
310
511
|
s = hub_nofail('merge', '--squash', branch)
|
311
512
|
if s.error?
|
312
513
|
cleanup_tmp_branch(tmpbranch, branch)
|
313
|
-
SugarJar::Log.
|
514
|
+
SugarJar::Log.debug(
|
314
515
|
'Failed to merge changes into current master. This means we could ' +
|
315
516
|
'not figure out if this is merged or not. Check manually and use ' +
|
316
517
|
"'git branch -D #{branch}' if it is safe to do so.",
|
@@ -353,8 +554,18 @@ class SugarJar
|
|
353
554
|
def gitup
|
354
555
|
SugarJar::Log.debug('Fetching upstream')
|
355
556
|
fetch_upstream
|
557
|
+
curr = current_branch
|
356
558
|
SugarJar::Log.debug('Rebasing')
|
357
559
|
base = tracked_branch
|
560
|
+
if curr != 'master' && base == "origin/#{curr}"
|
561
|
+
SugarJar::Log.warn(
|
562
|
+
"This branch is tracking origin/#{curr}, which is probably your " +
|
563
|
+
'downstream (where you push _to_) as opposed to your upstream ' +
|
564
|
+
'(where you pull _from_). This means that "sj up" is probably ' +
|
565
|
+
'rebasing on the wrong thing and doing nothing. You probably want ' +
|
566
|
+
'to do a "git branch -u upstream".',
|
567
|
+
)
|
568
|
+
end
|
358
569
|
s = hub_nofail('rebase', base)
|
359
570
|
s.error? ? nil : base
|
360
571
|
end
|
@@ -399,5 +610,20 @@ class SugarJar
|
|
399
610
|
end
|
400
611
|
@remote
|
401
612
|
end
|
613
|
+
|
614
|
+
def color(string, *colors)
|
615
|
+
if @color
|
616
|
+
pastel.decorate(string, *colors)
|
617
|
+
else
|
618
|
+
string
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
def pastel
|
623
|
+
@pastel ||= begin
|
624
|
+
require 'pastel'
|
625
|
+
Pastel.new
|
626
|
+
end
|
627
|
+
end
|
402
628
|
end
|
403
629
|
end
|
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
@@ -27,8 +27,55 @@ class SugarJar
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def hub_nofail(*args)
|
30
|
+
if %w{diff log grep branch}.include?(args[0]) &&
|
31
|
+
args.none? { |x| x.include?('color') }
|
32
|
+
args << (@color ? '--color' : '--no-color')
|
33
|
+
end
|
30
34
|
SugarJar::Log.trace("Running: hub #{args.join(' ')}")
|
31
|
-
Mixlib::ShellOut.new([which('hub')] + args).run_command
|
35
|
+
s = Mixlib::ShellOut.new([which('hub')] + args).run_command
|
36
|
+
if s.error?
|
37
|
+
# depending on hub version and possibly other things, STDERR
|
38
|
+
# is either "Requires authentication" or "Must authenticate"
|
39
|
+
case s.stderr
|
40
|
+
when /^(Must|Requires) authenticat/
|
41
|
+
SugarJar::Log.info(
|
42
|
+
'Hub was run but no github token exists. Will run "hub api user" ' +
|
43
|
+
"to force\nhub to authenticate...",
|
44
|
+
)
|
45
|
+
unless system(which('hub'), 'api', 'user')
|
46
|
+
SugarJar::Log.fatal(
|
47
|
+
'That failed, I will bail out. Hub needs to get a github ' +
|
48
|
+
'token. Try running "hub api user" (will list info about ' +
|
49
|
+
'your account) and try this again when that works.',
|
50
|
+
)
|
51
|
+
exit(1)
|
52
|
+
end
|
53
|
+
SugarJar::Log.info('Re-running original hub command...')
|
54
|
+
s = Mixlib::ShellOut.new([which('hub')] + args).run_command
|
55
|
+
when /^fatal: could not read Username/, /Anonymous access denied/
|
56
|
+
|
57
|
+
# On http(s) URLs, git may prompt for username/passwd
|
58
|
+
SugarJar::Log.info(
|
59
|
+
'Hub was run but git prompted for authentication. This probably ' +
|
60
|
+
"means you have\nused an http repo URL instead of an ssh one. It " +
|
61
|
+
"is recommended you reclone\nusing 'sj sclone' to setup your " +
|
62
|
+
"remotes properly. However, in the meantime,\nwe'll go ahead " +
|
63
|
+
"and re-run the command in a shell so you can type in the\n" +
|
64
|
+
'credentials.',
|
65
|
+
)
|
66
|
+
unless system(which('hub'), *args)
|
67
|
+
SugarJar::Log.fatal(
|
68
|
+
'That failed, I will bail out. You can either manually change ' +
|
69
|
+
'your remotes, or simply create a fresh clone with ' +
|
70
|
+
'"sj smartclone".',
|
71
|
+
)
|
72
|
+
exit(1)
|
73
|
+
end
|
74
|
+
SugarJar::Log.info('Re-running original hub command...')
|
75
|
+
s = Mixlib::ShellOut.new([which('hub')] + args).run_command
|
76
|
+
end
|
77
|
+
end
|
78
|
+
s
|
32
79
|
end
|
33
80
|
|
34
81
|
def hub(*args)
|
data/lib/sugarjar/version.rb
CHANGED
data/sugarjar.gemspec
CHANGED
@@ -8,6 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.email = ['phil@ipom.com']
|
9
9
|
spec.license = 'Apache-2.0'
|
10
10
|
spec.homepage = 'https://github.com/jaymzh/sugarjar'
|
11
|
+
spec.required_ruby_version = '>= 2.6.0'
|
11
12
|
docs = %w{README.md LICENSE Gemfile sugarjar.gemspec}
|
12
13
|
spec.extra_rdoc_files = docs
|
13
14
|
spec.executables << 'sj'
|
@@ -18,4 +19,5 @@ Gem::Specification.new do |spec|
|
|
18
19
|
|
19
20
|
spec.add_dependency 'mixlib-log'
|
20
21
|
spec.add_dependency 'mixlib-shellout'
|
22
|
+
spec.add_dependency 'pastel'
|
21
23
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sugarjar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Phil Dibowitz
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-02-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mixlib-log
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pastel
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
description:
|
42
56
|
email:
|
43
57
|
- phil@ipom.com
|
@@ -73,14 +87,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
73
87
|
requirements:
|
74
88
|
- - ">="
|
75
89
|
- !ruby/object:Gem::Version
|
76
|
-
version:
|
90
|
+
version: 2.6.0
|
77
91
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
92
|
requirements:
|
79
93
|
- - ">="
|
80
94
|
- !ruby/object:Gem::Version
|
81
95
|
version: '0'
|
82
96
|
requirements: []
|
83
|
-
rubygems_version: 3.
|
97
|
+
rubygems_version: 3.2.5
|
84
98
|
signing_key:
|
85
99
|
specification_version: 4
|
86
100
|
summary: A git/github helper script
|