sugarjar 0.0.1 → 0.0.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d2c4dd59aaf4843033f5c38df43b3fdfa556298708bc7f437f2923e00f9133c8
4
- data.tar.gz: 5edeeb730db94059fd10b115b192ed239782b797473eb3b1fcf5aa749ca23e95
3
+ metadata.gz: 2820df2090fc51cb383edb67d4f4f450d659bed13da44cdf34c6fc9242084a10
4
+ data.tar.gz: 6571f45c6609aa80441cea9102da98f85fef8056bfe1e7bce1d566a6ce6e1bb3
5
5
  SHA512:
6
- metadata.gz: 3869512549691df320f1dc344424b65944cd8cb501e25cd1a982f9b8d4d92cf608531147a8c5eb7a7437eee7644683ef9cf57e6035c44b61e4d247eec3331e03
7
- data.tar.gz: 159e663e59a6c2aa939a5e9aecb90ce811bb9a168476d842ced28eab7a9968b760daec22661455a7bdaf3ac75ecc11a1c706e2ea07a6368ab653bcc3a222a597
6
+ metadata.gz: 953d1400363c4ddddf92fb4c0f0cbbe239c60b0c19c9a47726e99c8f8bca48008079b903e9c921886a26dfe0b4c0d4495d89ae15cd32a298cfa7b5bf78317bc5
7
+ data.tar.gz: bea7b4b10e735797c288b3bece8ddad975f0ebc35f264e732bdda2d583d9380f719b7f471d1df1640f1bcd6e76a3d39e36456fa18d8ede04a10de0c40355784a
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'appbundler'
4
+ gem 'sugarjar', :path => '.'
5
+
6
+ group :test do
7
+ gem 'mdl'
8
+ gem 'rspec'
9
+ gem 'rubocop'
10
+ end
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
- # SugjarJar
1
+ # SugarJar
2
2
 
3
3
  ![CI](https://github.com/jaymzh/sugarjar/workflows/CI/badge.svg)
4
+ [![Gem Version](https://badge.fury.io/rb/sugarjar.svg)](https://badge.fury.io/rb/sugarjar)
4
5
 
5
6
  Welcome to SugarJar - a git/github helper. It leverages the amazing GitHub cli,
6
7
  [hub](https://hub.github.com/), so you'll need that installed.
@@ -12,84 +13,219 @@ the Phabricator workflow this aims to bring to the GitHub workflow.
12
13
  In particular there are a lot of helpers for using a squash-merge workflow that
13
14
  is poorly handled by the standard toolsets.
14
15
 
15
- ## Commands
16
+ If you miss Mondrian or Phabrictor - this is the tool for you!
16
17
 
17
- ### amend
18
+ If you don't, there's a ton of useful stuff for everyone!
18
19
 
19
- Amend the current commit. Alias for `git commit --amend`. Accepts other
20
- arguments such as `-a` or files.
20
+ ## Auto cleanup squash-merged branches
21
21
 
22
- ### amendq, qamend
22
+ It is common for a PR to go back and forth with a variety of nits, lint fixes,
23
+ typos, etc. that can muddy history. So many projects will "squash and merge"
24
+ when they accept a pull request. Howevet, that means `git branch -d <branch>`
25
+ doesn't work. Git will tell you the branch isn't fully merged. You can, of
26
+ course `git branch -D <branch>`, but that does no safety checks at all, it
27
+ forces the deletion.
23
28
 
24
- Same as `amend` but without changing the message. Alias for `git commit --amend
25
- --no-edit`.
29
+ Enter `sj bclean` - it determines of the contents of your branch has been merge
30
+ and safely deletes if so.
26
31
 
27
- ### bclean
32
+ ``` shell
33
+ sj bclean
34
+ ```
28
35
 
29
- If safe, delete the current branch. Unlike `git branch -d`, bclean can handle
30
- squash-merged branches. Think of it as a smarter `git branch -d`.
36
+ Will delete a branch, if it has been merged, **even if it was squash-merged**.
31
37
 
32
- ### bcleanall
38
+ You can pass it a branch if you'd like (it defaults to the branch you're on):
39
+ `sj bclean <branch>`.
33
40
 
34
- Walk all branches, and try to delete them if it's safe. See `bclean` for
35
- details.
41
+ But it gets better! You can use `sj bcleanall` to remove all branches that have
42
+ been merged:
36
43
 
37
- ### binfo
44
+ ```shell
45
+ $ git branch
46
+ * argparse
47
+ master
48
+ feature
49
+ hubhost
50
+ $ git bcleanall
51
+ Skipping branch argparse - there are unmerged commits
52
+ Reaped branch feature
53
+ Reaped branch hubhost
54
+ ```
38
55
 
39
- Verbose information about the current branch.
56
+ ## Smarter clones and remotes
40
57
 
41
- ### br
58
+ There's a pattern to every new repo we want to contribute to. First we fork,
59
+ then we clone the fork, then we add a remote of the upstream repo. It's
60
+ monotonous. SugarJar does this for you:
42
61
 
43
- Verbose branch list. An alias for `git branch -v`.
62
+ ```shell
63
+ sj smartclone jaymzh/sugarjar
64
+ ```
44
65
 
45
- ### feature
66
+ (also `sj sclone`)
46
67
 
47
- Create a "feature branch." It's morally equivalent to `git checkout -b` except
48
- it defaults to creating it based on some form of 'master' instead of your
49
- current branch. In order of preference it will be `upstream/master`,
50
- `origin/master`, or `master`, depending upon what remotes are available.
68
+ This will:
51
69
 
52
- ### forcepush, fpush
70
+ * Make a fork of the repo, if you don't already have one
71
+ * Clone your fork
72
+ * Add the original as an 'upstream' remote
53
73
 
54
- The same as `smartpush`, but uses `--force-with-lease`. This is a "safer" way
55
- of doing force-pushes and is the recommended way to push after rebasing or
56
- amending. Never do this to shared branches. Very convenient for keeping the
57
- branch behind a pull-request clean.
74
+ Note that it takes `hub`s short-names for repos. No need to specify a full URL,
75
+ just a $org/$repo.
58
76
 
59
- ### lint
77
+ Like `git clone`, `sj sclone` will accept an additional arguement as the
78
+ destination directory to clone to. It will also pass any other unknown options
79
+ to `git clone` under the hood.
60
80
 
61
- Run any linters configured in `.sugarjar.yaml`.
81
+ ## Work with stacked branches more easily
62
82
 
63
- ### smartclone, sclone
83
+ It's important to break changes into reviewable chunks, but working with
84
+ stacked branches can be confusing. Enter `binfo` - it gives you a view of your
85
+ current branch all the way up to master. In this example imagine we have a
86
+ branch structure like:
64
87
 
65
- A smart wrapper to `git clone` that handles forking and managing remotes for
66
- you. It will clone a git repository using hub-style short name (`$org/$repo`).
67
- If the org of the repository is not the same as your github-user then it will
68
- fork the repo for you to your account (if not already done) and then setup your
69
- remotes so that `origin` is your fork and `upstream` is the upstream.
88
+ ```text
89
+ +- test2.1
90
+ /
91
+ master --- test --- test2 --- test3
92
+ ```
70
93
 
71
- ### smartpush, spush
94
+ This is what `binfo` on test3 looks like:
72
95
 
73
- A smart wrapper to `git push` that runs whatever is defined in `on_push` in
74
- `.sugarjar.yml`, and only pushes if they succeed.
96
+ ```shell
97
+ $ sj binfo
98
+ * e451865 (HEAD -> test3) test3
99
+ * e545b41 (test2) test2
100
+ * c808eae (test1) test1
101
+ o 44cf9e2 (origin/master, origin/HEAD, master) Lint/gemspec cleanups
102
+ ```
75
103
 
76
- It will also allow you to not specify a remote or branch, and will default to
77
- `origin` and whatever your current local branch name is.
104
+ while `binfo` on test2.1 looks like:
78
105
 
79
- ### unit
106
+ ```shell
107
+ $ sj binfo
108
+ * 36d0136 (HEAD -> test2.1) test2.1
109
+ * e545b41 (test2) test2
110
+ * c808eae (test1) test1
111
+ o 44cf9e2 (origin/master, origin/HEAD, master) Lint/gemspec cleanups
112
+ ```
80
113
 
81
- Run any unitests configured in `.sugarjar.yaml`.
114
+ ## Have a better lint/unittest experience!
82
115
 
83
- ### up
116
+ Ever made a PR, only to find out later that it failed tests because of some
117
+ small lint issue? Not anymore! SJ can be configured to run things before
118
+ pushing. For example,in the SugarJar repo, we have it run Rubocop (ruby lint)
119
+ and Markdownlint "on_push". If those fail, it lets you know and doesn't push.
84
120
 
85
- Rebase the current branch on the branch it's tracking, or if it's tracking one
86
- then, otherise `upstream/master` if it exists, or `origin/master`.
121
+ You can configure SugarJar to tell how how to run both lints and unittests for
122
+ a given repo and if one or both should be run prior to pushing.
87
123
 
88
- ### upall
124
+ The details on the config file format is below, but we provide three commands:
89
125
 
90
- Same as `up`, but for all branches.
126
+ ```shell
127
+ git lint
128
+ ```
91
129
 
92
- ## User Configuration
130
+ Run all linters.
131
+
132
+ ```shell
133
+ git unit
134
+ ```
135
+
136
+ Run all unittests.
137
+
138
+ ```shell
139
+ git smartpush # or spush
140
+ ```
141
+
142
+ Run configured push-time actions (nothing, lint, unit, both), and do not
143
+ push if any of them fail.
144
+
145
+ ## Better push defaults
146
+
147
+ In addition to running pre-push tests for you `smartpush` also picks smart
148
+ defaults for push. So if you `sj spush` with no arguements, it uses the
149
+ `origin` remote and the same branch name you're on as the remote branch.
150
+
151
+ ## Cleaning up your own history
152
+
153
+ Perhaps you contribute to a project that prefers to use merge commits, so you
154
+ like to clean up your own history. This is often difficult to get right - a
155
+ combination of rebases, amends and force pushes. We provide two commands here
156
+ to help.
157
+
158
+ The first is pretty straight forward and is basically just an alias: `sj
159
+ amend`. It will ammend whatever you want to the most recent commit (just an
160
+ alias for `git commit --amend`). It has a partner `qamend` (or `amendq` if you
161
+ prefer) that will do so without prompting to update your commit message.
162
+
163
+ So now you've rebased or amended, pushing becomes challening. You can `git push
164
+ --force`, but everyone knows that's incredibly dangerous. Is there a better
165
+ way? There is! Git provides `git push --force-with-lease` - it checks to make
166
+ sure you're up-to-date with the remote before forcing the push. But man that
167
+ command is a mouthful! Enter `sj fpush`. It has all the smarts of `sj
168
+ smartpush` (runs configured pre-push actions), but adds `--force-with-lease` to
169
+ the command!
170
+
171
+ ## Better feature branches
172
+
173
+ When you want to start a new feature, you want to start developing against
174
+ latest. That's why `sj feature` defaults to creating a branch against what we
175
+ call "most master". That is, `upstream/master` if it exists, otherwise
176
+ `origin/master` if that exists, otherwise `master`. You can pass in an
177
+ additional arguement to base it off of something else.
178
+
179
+ ```shell
180
+ $ git branch
181
+ master
182
+ test1
183
+ test2
184
+ * test2.1
185
+ test3
186
+ $ sj feature test-branch
187
+ Created feature branch test-branch based on origin/master
188
+ $ sj feature dependent-feature test-branch
189
+ Created feature branch dependent-feature based on test-branch
190
+ ```
191
+
192
+ ## And more!
193
+
194
+ See `sj help` for more commands!
195
+
196
+ ## Using SugarJar as a git wrapper
197
+
198
+ SugarJar, by default, will pass any command it doesn't know straight to `hub`
199
+ (which passes commands **it** doesn't know to `git`). As such you can alias it
200
+ to `git` and just have a super-git.
201
+
202
+ ```shell
203
+ $ alias git=sj
204
+ $ git config -l | grep color
205
+ color.diff=auto
206
+ color.status=auto
207
+ color.branch=auto
208
+ color.branch.current=yellow reverse
209
+ color.branch.local=yellow
210
+ color.branch.remote=green
211
+ $ git br
212
+ * dependent-feature 44cf9e2 Lint/gemspec cleanups
213
+ master 44cf9e2 Lint/gemspec cleanups
214
+ test-branch 44cf9e2 Lint/gemspec cleanups
215
+ test1 c808eae [ahead 1] test1
216
+ test2 e545b41 test2
217
+ test2.1 c1831b3 test2.1
218
+ test3 e451865 test3
219
+ ```
220
+
221
+ It's for this reason that SugarJar doesn't have conflicting command names. You
222
+ can turn off fallthru by setting `fallthru: false` in your config.
223
+
224
+ The only command we "override" is `version`, in which case we not only print
225
+ our version, but also call `hub version` which prints its version and calls
226
+ `git version` too!
227
+
228
+ ## Configuration
93
229
 
94
230
  Sugarjar will read in both a system-level config file
95
231
  (`/etc/sugarjar/config.yaml`) and a user-level config file
@@ -110,17 +246,19 @@ troubleshoot configuration parsing.
110
246
  ## Repository Configuration
111
247
 
112
248
  Sugarjar looks for a `.sugarjar.yaml` in the root of the repository to tell it
113
- how to handle repo-specific things. Currently there are only three
114
- configurations accepted:
249
+ how to handle repo-specific things. Currently there options are:
115
250
 
116
- * lint - A list of scripts to run on `sj lint`. These should be linters like
251
+ * `lint` - A list of scripts to run on `sj lint`. These should be linters like
117
252
  rubocop or pyflake.
118
- * unit - A list of scripts to run on `sj unit`. These should be unittest
253
+ * `unit` - A list of scripts to run on `sj unit`. These should be unittest
119
254
  runners like rspec or pyunit.
120
- * on_push - A list of types (`lint`, `unit`) of checks to run before pushing.
255
+ * `on_push` - A list of types (`lint`, `unit`) of checks to run before pushing.
121
256
  It is highly recommended this is only `lint`. The goal here is to allow for
122
257
  the user to get quick stylistic feedback before pushing their branch to avoid
123
258
  the push-fix-push-fix loop.
259
+ * `commit_template` - A path to a commit template to set in the `commit.template`
260
+ git config for this repo. Should be either a fully-qualified path, or a path
261
+ relative to the repo root.
124
262
 
125
263
  Example configuration:
126
264
 
@@ -131,19 +269,52 @@ unit:
131
269
  - scripts/unit
132
270
  on_push:
133
271
  - lint
272
+ commit_template: .commit-template.txt
273
+ ```
274
+
275
+ ### Commit Templates
276
+
277
+ While GitHub provides a way to specify a pull-request template by putting the
278
+ right file into a repo, there is no way to tell git to automatically pick up a
279
+ commit template by dropping a file in the repo. Users must do something like:
280
+ `git config commit.template <file>`. Making each developer do this is error
281
+ prone, so this setting will automatically set this up for each developer.
282
+
283
+ ## Enterprise GitHub
284
+
285
+ Like `hub`, SugarJar supports GitHub Enterprise. In fact, we provide extra
286
+ features just for it.
287
+
288
+ We recommend the global or user config specify the `github_host`. However, most
289
+ users will also have a few repos from upstream so always specifying a
290
+ `github_host` is sub-optimal.
291
+
292
+ So, when you overwrite the `github_host` on the command line, we go ahead and
293
+ set the `hub.host` git config in that single repo so that it'll "just work"
294
+ from there on out.
295
+
296
+ In other words, assuming your global SJ config has `github_host:
297
+ github.sample.com`, and the you clone sugarjar with:
298
+
299
+ ```shell
300
+ sj clone jaymzh/sugarjar --github-host githuh.com
134
301
  ```
135
302
 
303
+ We will add the `hub.host` to the `sugarjar` clone so that future `hub` or `sj`
304
+ commands work without needing to specify..
305
+
136
306
  ## FAQ
137
307
 
138
308
  Why the name SugarJar?
139
309
 
140
- It's mostly a backranym. Like jellyfish, I wanted two letters that were on
141
- home row on different sides of the keyboard to make it easy to type. I looked
142
- at the possible options that where there and not taken and tried to find one
143
- I could make an appropriate name out of. Since this utility adds lots of sugar
144
- to git and github, it seemed appropriate.
310
+ It's mostly a backranym. Like jellyfish, I wanted two letters that were on home
311
+ row on different sides of the keyboard to make it easy to type. I looked at the
312
+ possible options that where there and not taken and tried to find one I could
313
+ make an appropriate name out of. Since this utility adds lots of sugar to git
314
+ and github, it seemed appropriate.
145
315
 
146
316
  Why did you use `hub` instead of the newer `gh` CLI?
147
317
 
148
- `gh` is less feature-rich (currently). I'm also considering making this optionally
149
- act as a wrapper to `hub` the way `hub` can be a wrapper to `git`.
318
+ `gh` is still new and not yet as feature rich as `hub`. Also I wanted SugarJar
319
+ to be able to be a git wrapper, and so wrapping `hub` allows us to do that but
320
+ wrapping `gh` does not.
data/bin/sj ADDED
@@ -0,0 +1,239 @@
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('--version') do
65
+ puts SugarJar::VERSION
66
+ exit
67
+ end
68
+
69
+ # rubocop:disable Layout/HeredocIndentation
70
+ opts.separator <<COMMANDS
71
+
72
+ COMMANDS:
73
+ amend
74
+ Amend the current commit. Alias for "git commit --amend".
75
+ Accepts other arguments such as "-a" or files.
76
+
77
+ amendq, qamend
78
+ Same as "amend" but without changing the message. Alias for
79
+ "git commit --amend --no-edit".
80
+
81
+ bclean
82
+ If safe, delete the current branch. Unlike "git branch -d",
83
+ bclean can handle squash-merged branches. Think of it as
84
+ a smarter "git branch -d".
85
+
86
+ bcleanall
87
+ Walk all branches, and try to delete them if it's safe. See
88
+ "bclean" for details.
89
+
90
+ binfo
91
+ Verbose information about the current branch.
92
+
93
+ br
94
+ Verbose branch list. An alias for "git branch -v".
95
+
96
+ feature
97
+ Create a "feature" branch. It's morally equivalent to
98
+ "git checkout -b" except it defaults to creating it based on
99
+ some form of 'master' instead of your current branch. In order
100
+ of preference it will be upstream/master, origin/master, master,
101
+ depending upon what remotes are available.
102
+
103
+ forcepush, fpush
104
+ The same as "smartpush", but uses "--force-with-lease". This is
105
+ a "safer" way of doing force-pushes and is the recommended way
106
+ to push after rebasing or amending. Never do this to shared
107
+ branches. Very convenient for keeping the branch behind a pull-
108
+ request clean.
109
+
110
+ lint
111
+ Run any linters configured in .sugarjar.yaml.
112
+
113
+ smartclone, sclone
114
+ A smart wrapper to "git clone" that handles forking and managing
115
+ remotes for you.
116
+ It will clone a git repository using hub-style short name
117
+ ("$org/$repo"). If the org of the repository is not the same
118
+ as your github-user then it will fork the repo for you to
119
+ your account (if not already done) and then setup your remotes
120
+ so that "origin" is your fork and "upstream" is the upstream.
121
+
122
+ smartpush, spush
123
+ A smart wrapper to "git push" that runs whatever is defined in
124
+ "on_push" in .sugarjar.yml, and only pushes if they succeed.
125
+
126
+ unit
127
+ Run any unitests configured in .sugarjar.yaml.
128
+
129
+ up
130
+ Rebase the current branch on upstream/master or origin/master.
131
+
132
+ upall
133
+ Same as "up", but for all branches.
134
+
135
+ verson
136
+ Print the version of sugarjar, and then run 'hub version'
137
+ to show the hub and git versions.
138
+ COMMANDS
139
+
140
+ # rubocop:enable Layout/HeredocIndentation
141
+ end
142
+
143
+ # we make a copy of these because we will assign back to the ARGV
144
+ # we parse later. We also need a pristine copy in case we want to
145
+ # run git as we were called.
146
+ argv_copy = ARGV.dup
147
+
148
+ # We don't have options yet, but we need an instance of SJ in order
149
+ # to list public methods. We will recreate it
150
+ sj = SugarJar::Commands.new(options.merge({ 'no_change' => true }))
151
+ extra_opts = []
152
+
153
+ # as with above, this can't go into 'options', until after we parse
154
+ # the command line args
155
+ config = SugarJar::Config.config
156
+
157
+ valid_commands = sj.public_methods - Object.public_methods
158
+
159
+ is_valid_command = ARGV.any? { |arg| valid_commands.include?(arg.to_s.to_sym) }
160
+
161
+ # if we're configured to fall thru and the subcommand isn't one
162
+ # we recognize, don't parse the options as they may be different
163
+ # than git's. For example `git config -l` - we error because we
164
+ # require an arguement to `-l`.
165
+ if config['fallthru'] && !is_valid_command
166
+ SugarJar::Log.debug(
167
+ 'Skipping option parsing: fall-thru is set and we do not recognize ' +
168
+ 'any subcommands',
169
+ )
170
+ else
171
+ # We want to allow people to pass in extra args to be passed to
172
+ # git commands, but OptionParser doesn't easily allow this. So we
173
+ # loop over it, catching exceptions.
174
+ begin
175
+ # HOWEVER, anytime it throws an exception, for some reason, it clears
176
+ # out all of ARGV, or whatever you passed to as ARGV.
177
+ #
178
+ # This not only prevents further parsing, but also means we lose
179
+ # any non-option arguements (like the subcommand!)
180
+ #
181
+ # So we save a copy, and if we throw an exception, save the option that
182
+ # caused it, remove that option from our copy, and then re-populate argv
183
+ # with what's left.
184
+ #
185
+ # By doing this we not only get to parse all the options properly and
186
+ # save unknown ones, but non-option arguements, which OptionParser
187
+ # normally leaves in ARGV stay in ARGV.
188
+ saved_argv = argv_copy.dup
189
+ parser.parse!(argv_copy)
190
+ rescue OptionParser::InvalidOption => e
191
+ SugarJar::Log.debug("Saving unknown argument #{e.args}")
192
+ extra_opts += e.args
193
+
194
+ # e.args is an array, but it's only ever one arguement per exception
195
+ saved_argv.delete(e.args.first)
196
+ argv_copy = saved_argv.dup
197
+ SugarJar::Log.debug(
198
+ "Continuing option parsing with remaining ARGV: #{argv_copy}",
199
+ )
200
+ retry
201
+ end
202
+ end
203
+
204
+ if ARGV.empty?
205
+ puts parser
206
+ exit
207
+ end
208
+
209
+ options = config.merge(options)
210
+
211
+ # Recreate SJ with all of our options
212
+ SugarJar::Log.level = options['log_level'].to_sym if options['log_level']
213
+ sj = SugarJar::Commands.new(options)
214
+
215
+ subcommand = argv_copy.reject { |x| x.start_with?('-') }.first
216
+ argv_copy.delete(subcommand)
217
+ SugarJar::Log.debug("subcommand is #{subcommand}")
218
+
219
+ # Extra options we got, plus any left over arguements are what we
220
+ # pass to Commands so they can be passed to git as necessary
221
+ extra_opts += argv_copy
222
+ SugarJar::Log.debug("extra unknown options: #{extra_opts}")
223
+
224
+ if subcommand == 'help'
225
+ puts parser
226
+ exit
227
+ end
228
+
229
+ if is_valid_command
230
+ SugarJar::Log.debug(
231
+ "running #{subcommand}; extra opts: #{extra_opts.join(', ')}",
232
+ )
233
+ sj.send(subcommand.to_sym, *extra_opts)
234
+ elsif options['fallthru']
235
+ SugarJar::Log.debug("Falling thru to: hub #{ARGV.join(' ')}")
236
+ exec('hub', *ARGV)
237
+ else
238
+ SugarJar::Log.error("No such subcommand: #{subcommand}")
239
+ end
@@ -11,10 +11,14 @@ class SugarJar
11
11
  include SugarJar::Util
12
12
 
13
13
  def initialize(options)
14
+ SugarJar::Log.debug("Commands.initialize options: #{options}")
14
15
  @ghuser = options['github_user']
15
16
  @ghhost = options['github_host']
16
17
  @repo_config = SugarJar::RepoConfig.config
18
+ return if options['no_change']
19
+
17
20
  set_hub_host if @ghhost
21
+ set_commit_template if @repo_config['commit_template']
18
22
  end
19
23
 
20
24
  def feature(name, base = nil)
@@ -22,6 +26,8 @@ class SugarJar
22
26
  SugarJar::Log.debug("Feature: #{name}, #{base}")
23
27
  die("#{name} already exists!") if all_branches.include?(name)
24
28
  base ||= most_master
29
+ base_pieces = base.split('/')
30
+ hub('fetch', base_pieces[0]) if base_pieces.length > 1
25
31
  hub('checkout', '-b', name, base)
26
32
  SugarJar::Log.info("Created feature branch #{name} based on #{base}")
27
33
  end
@@ -38,22 +44,30 @@ class SugarJar
38
44
 
39
45
  def bcleanall
40
46
  assert_in_repo
47
+ curr = current_branch
41
48
  all_branches.each do |branch|
42
49
  next if branch == 'master'
43
50
 
44
51
  # rubocop:disable Style/Next
45
52
  unless clean_branch(branch)
46
53
  SugarJar::Log.info(
47
- "Skipping branch #{branch} - there are unmerged commits"
54
+ "Skipping branch #{branch} - there are unmerged commits",
48
55
  )
49
56
  end
50
57
  # rubocop:enable Style/Next
51
58
  end
59
+
60
+ # Return to the branch we were on, or master
61
+ if all_branches.include?(curr)
62
+ hub('checkout', curr)
63
+ else
64
+ hub('checkout', 'master')
65
+ end
52
66
  end
53
67
 
54
- def co(name)
68
+ def co(*args)
55
69
  assert_in_repo
56
- hub('checkout', name)
70
+ hub('checkout', *args)
57
71
  end
58
72
 
59
73
  def br
@@ -82,7 +96,7 @@ class SugarJar
82
96
  def amend(*args)
83
97
  assert_in_repo
84
98
  # This cannot use shellout since we need a full terminal for the editor
85
- exit(system('/usr/bin/git', 'commit', '--amend', *args))
99
+ exit(system(which('git'), 'commit', '--amend', *args))
86
100
  end
87
101
 
88
102
  def qamend(*args)
@@ -104,7 +118,7 @@ class SugarJar
104
118
  else
105
119
  SugarJar::Log.error(
106
120
  "Failed to rebase #{branch}, aborting that and moving to next " +
107
- 'branch'
121
+ 'branch',
108
122
  )
109
123
  hub('rebase', '--abort')
110
124
  end
@@ -127,7 +141,7 @@ class SugarJar
127
141
 
128
142
  org = File.basename(File.dirname(repo))
129
143
  if org == @ghuser
130
- put 'Cloned forked or self-owned repo. Not creating "upstream".'
144
+ puts 'Cloned forked or self-owned repo. Not creating "upstream".'
131
145
  return
132
146
  end
133
147
 
@@ -152,7 +166,7 @@ class SugarJar
152
166
  end
153
167
 
154
168
  def unit
155
- exit(1) unless run_check('lint')
169
+ exit(1) unless run_check('unit')
156
170
  end
157
171
 
158
172
  def smartpush(remote = nil, branch = nil)
@@ -202,14 +216,60 @@ class SugarJar
202
216
  if current == @ghost
203
217
  SugarJar::Log.debug('Repo hub.host already set correctly')
204
218
  else
205
- SugarJar::Log.info(
206
- "Overwriting repo hub.host from #{current} to #{@ghhost}"
219
+ # Even though we have an explicit config, in most cases, it
220
+ # comes from a global or user config, but the config in the
221
+ # local repo we likely set. So we'd just constantly revert that.
222
+ SugarJar::Log.debug(
223
+ "Not overwriting repo hub.host. Already set to #{current}. " +
224
+ "To change it, run `git config --local --add hub.host #{@ghhost}`",
207
225
  )
208
226
  end
227
+ return
209
228
  end
210
229
  hub('config', '--local', '--add', 'hub.host', @ghhost)
211
230
  end
212
231
 
232
+ def set_commit_template
233
+ unless in_repo
234
+ SugarJar::Log.debug('Skipping set_commit_template: not in repo')
235
+ return
236
+ end
237
+
238
+ realpath = if @repo_config['commit_template'].start_with?('/')
239
+ @repo_config['commit_template']
240
+ else
241
+ "#{repo_root}/#{@repo_config['commit_template']}"
242
+ end
243
+ unless File.exist?(realpath)
244
+ die(
245
+ "Repo config specifies #{@repo_config['commit_template']} as the " +
246
+ 'commit template, but that file does not exist.',
247
+ )
248
+ end
249
+
250
+ s = hub_nofail('config', '--local', 'commit.template')
251
+ unless s.error?
252
+ current = s.stdout.strip
253
+ if current == @repo_config['commit_template']
254
+ SugarJar::Log.debug('Commit template already set correctly')
255
+ return
256
+ else
257
+ SugarJar::Log.warn(
258
+ "Updating repo-specific commit template from #{current} " +
259
+ "to #{@repo_config['commit_template']}",
260
+ )
261
+ end
262
+ end
263
+
264
+ SugarJar::Log.debug(
265
+ 'Setting repo-specific commit template to ' +
266
+ "#{@repo_config['commit_template']} per sugarjar repo config.",
267
+ )
268
+ hub(
269
+ 'config', '--local', 'commit.template', @repo_config['commit_template']
270
+ )
271
+ end
272
+
213
273
  def run_check(type)
214
274
  unless @repo_config[type]
215
275
  SugarJar::Log.debug("No #{type} configured. Returning success")
@@ -224,16 +284,20 @@ class SugarJar
224
284
  return false
225
285
  end
226
286
  s = Mixlib::ShellOut.new(check).run_command
227
- if s.error?
228
- SugarJar::Log.info("#{type} #{check} failed\n#{s.stdout}")
229
- return false
230
- end
287
+ next unless s.error?
288
+
289
+ SugarJar::Log.info(
290
+ "#{type} #{check} failed, output follows (use debug for more)\n" +
291
+ s.stdout.to_s,
292
+ )
293
+ SugarJar::Log.debug(s.format_for_exception)
294
+ return false
231
295
  end
232
296
  end
233
297
  end
234
298
 
235
299
  def run_prepush
236
- @repo_config['on_push'].each do |item|
300
+ @repo_config['on_push']&.each do |item|
237
301
  SugarJar::Log.debug("Running on_push check type #{item}")
238
302
  unless send(:run_check, item)
239
303
  SugarJar::Log.info("Push check #{item} failed.")
@@ -287,7 +351,7 @@ class SugarJar
287
351
  end
288
352
  if out.length.zero?
289
353
  SugarJar::Log.debug(
290
- "cherry-pick shows branch #{branch} obviously safe to delete"
354
+ "cherry-pick shows branch #{branch} obviously safe to delete",
291
355
  )
292
356
  return true
293
357
  end
@@ -303,10 +367,10 @@ class SugarJar
303
367
  s = hub_nofail('merge', '--squash', branch)
304
368
  if s.error?
305
369
  cleanup_tmp_branch(tmpbranch, branch)
306
- error(
370
+ SugarJar::Log.error(
307
371
  'Failed to merge changes into current master. This means we could ' +
308
372
  'not figure out if this is merged or not. Check manually and use ' +
309
- "'git branch -D #{branch}' if it is safe to do so."
373
+ "'git branch -D #{branch}' if it is safe to do so.",
310
374
  )
311
375
  return false
312
376
  end
@@ -317,12 +381,12 @@ class SugarJar
317
381
  cleanup_tmp_branch(tmpbranch, branch)
318
382
  if out.empty?
319
383
  SugarJar::Log.debug(
320
- 'After squash-merging, this branch appears safe to delete'
384
+ 'After squash-merging, this branch appears safe to delete',
321
385
  )
322
386
  true
323
387
  else
324
388
  SugarJar::Log.debug(
325
- 'After squash-merging, this branch is NOT fully merged to master'
389
+ 'After squash-merging, this branch is NOT fully merged to master',
326
390
  )
327
391
  false
328
392
  end
@@ -346,8 +410,18 @@ class SugarJar
346
410
  def gitup
347
411
  SugarJar::Log.debug('Fetching upstream')
348
412
  fetch_upstream
413
+ curr = current_branch
349
414
  SugarJar::Log.debug('Rebasing')
350
415
  base = tracked_branch
416
+ if curr != 'master' && base == "origin/#{curr}"
417
+ SugarJar::Log.warn(
418
+ "This branch is tracking origin/#{curr}, which is probably your " +
419
+ 'downstream (where you push _to_) as opposed to your upstream ' +
420
+ '(where you pull _from_). This means that "sj up" is probably ' +
421
+ 'rebasing on the wrong thing and doing nothing. You probably want ' +
422
+ 'to do a "git branch -u upstream".',
423
+ )
424
+ end
351
425
  s = hub_nofail('rebase', base)
352
426
  s.error? ? nil : base
353
427
  end
@@ -6,7 +6,7 @@ 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
 
@@ -22,7 +22,9 @@ class SugarJar
22
22
  c = DEFAULTS.dup
23
23
  _find_ordered_files.each do |f|
24
24
  SugarJar::Log.debug("Loading config #{f}")
25
- c.merge!(YAML.safe_load(File.read(f)))
25
+ data = YAML.safe_load(File.read(f))
26
+ # an empty file is a `nil` which you can't merge
27
+ c.merge!(YAML.safe_load(File.read(f))) if data
26
28
  SugarJar::Log.debug("Modified config: #{c}")
27
29
  end
28
30
  c
@@ -3,9 +3,51 @@ require_relative 'log'
3
3
  class SugarJar
4
4
  # Some common methods needed by other classes
5
5
  module Util
6
+ # Finds the first entry in the path for a binary and checks
7
+ # to make sure it's not us (i.e. we may be linked to as 'git'
8
+ # or 'hub', but when we are calling that, we don't want ourselves.
9
+ def which_nofail(cmd)
10
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |dir|
11
+ p = File.join(dir, cmd)
12
+ # if it exists, and it is executable and is not us...
13
+ if File.exist?(p) && File.executable?(p) &&
14
+ File.basename(File.realpath(p)) != 'sj'
15
+ return p
16
+ end
17
+ end
18
+ false
19
+ end
20
+
21
+ def which(cmd)
22
+ path = which_nofail(cmd)
23
+ return path if path
24
+
25
+ SugarJar::Log.fatal("Could not find #{cmd} in your path")
26
+ exit(1)
27
+ end
28
+
6
29
  def hub_nofail(*args)
7
30
  SugarJar::Log.trace("Running: hub #{args.join(' ')}")
8
- Mixlib::ShellOut.new(['/usr/bin/hub'] + args).run_command
31
+ s = Mixlib::ShellOut.new([which('hub')] + args).run_command
32
+ # depending on hub version and possibly other things, STDERR
33
+ # is either "Requires authentication" or "Must authenticate"
34
+ if s.error? && s.stderr =~ /^(Must|Requires) authenticat/
35
+ SugarJar::Log.info(
36
+ 'Hub was run but no github token exists. Will run "hub api user" ' +
37
+ "to force\nhub to authenticate...",
38
+ )
39
+ unless system(which('hub'), 'api', 'user')
40
+ SugarJar::Log.fatal(
41
+ 'That failed, I will bail out. Hub needs to get a github ' +
42
+ 'token. Try running "hub api user" (will list info about ' +
43
+ 'your account) and try this again when that works.',
44
+ )
45
+ exit(1)
46
+ end
47
+ SugarJar::Log.info('Re-running original hub command...')
48
+ s = Mixlib::ShellOut.new([which('hub')] + args).run_command
49
+ end
50
+ s
9
51
  end
10
52
 
11
53
  def hub(*args)
@@ -1,3 +1,3 @@
1
1
  class SugarJar
2
- VERSION = '0.0.1'.freeze
2
+ VERSION = '0.0.6'.freeze
3
3
  end
@@ -0,0 +1,21 @@
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
+ docs = %w{README.md LICENSE Gemfile sugarjar.gemspec}
12
+ spec.extra_rdoc_files = docs
13
+ spec.executables << 'sj'
14
+ spec.files =
15
+ Dir.glob('lib/sugarjar/*.rb') +
16
+ Dir.glob('bin/*') +
17
+ docs
18
+
19
+ spec.add_dependency 'mixlib-log'
20
+ spec.add_dependency 'mixlib-shellout'
21
+ 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.1
4
+ version: 0.0.6
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-05 00:00:00.000000000 Z
11
+ date: 2020-07-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mixlib-log
@@ -38,79 +38,29 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: yaml
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'
55
- - !ruby/object:Gem::Dependency
56
- name: bundler
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: mdl
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
- - !ruby/object:Gem::Dependency
84
- name: rubocop
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
41
  description:
98
42
  email:
99
43
  - phil@ipom.com
100
- executables: []
44
+ executables:
45
+ - sj
101
46
  extensions: []
102
47
  extra_rdoc_files:
103
48
  - README.md
104
49
  - LICENSE
50
+ - Gemfile
51
+ - sugarjar.gemspec
105
52
  files:
53
+ - Gemfile
106
54
  - LICENSE
107
55
  - README.md
56
+ - bin/sj
108
57
  - lib/sugarjar/commands.rb
109
58
  - lib/sugarjar/config.rb
110
59
  - lib/sugarjar/log.rb
111
60
  - lib/sugarjar/repoconfig.rb
112
61
  - lib/sugarjar/util.rb
113
62
  - lib/sugarjar/version.rb
63
+ - sugarjar.gemspec
114
64
  homepage: https://github.com/jaymzh/sugarjar
115
65
  licenses:
116
66
  - Apache-2.0
@@ -130,7 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
130
80
  - !ruby/object:Gem::Version
131
81
  version: '0'
132
82
  requirements: []
133
- rubygems_version: 3.0.3
83
+ rubygems_version: 3.1.2
134
84
  signing_key:
135
85
  specification_version: 4
136
86
  summary: A git/github helper script