sugarjar 0.0.1 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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