sugarjar 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d2c4dd59aaf4843033f5c38df43b3fdfa556298708bc7f437f2923e00f9133c8
4
+ data.tar.gz: 5edeeb730db94059fd10b115b192ed239782b797473eb3b1fcf5aa749ca23e95
5
+ SHA512:
6
+ metadata.gz: 3869512549691df320f1dc344424b65944cd8cb501e25cd1a982f9b8d4d92cf608531147a8c5eb7a7437eee7644683ef9cf57e6035c44b61e4d247eec3331e03
7
+ data.tar.gz: 159e663e59a6c2aa939a5e9aecb90ce811bb9a168476d842ced28eab7a9968b760daec22661455a7bdaf3ac75ecc11a1c706e2ea07a6368ab653bcc3a222a597
data/LICENSE ADDED
@@ -0,0 +1,201 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
@@ -0,0 +1,149 @@
1
+ # SugjarJar
2
+
3
+ ![CI](https://github.com/jaymzh/sugarjar/workflows/CI/badge.svg)
4
+
5
+ Welcome to SugarJar - a git/github helper. It leverages the amazing GitHub cli,
6
+ [hub](https://hub.github.com/), so you'll need that installed.
7
+
8
+ SugarJar is inspired by [arcanist](https://github.com/phacility/arcanist), and
9
+ it's replacement at Facebook, JellyFish. Many of the features they provide for
10
+ the Phabricator workflow this aims to bring to the GitHub workflow.
11
+
12
+ In particular there are a lot of helpers for using a squash-merge workflow that
13
+ is poorly handled by the standard toolsets.
14
+
15
+ ## Commands
16
+
17
+ ### amend
18
+
19
+ Amend the current commit. Alias for `git commit --amend`. Accepts other
20
+ arguments such as `-a` or files.
21
+
22
+ ### amendq, qamend
23
+
24
+ Same as `amend` but without changing the message. Alias for `git commit --amend
25
+ --no-edit`.
26
+
27
+ ### bclean
28
+
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`.
31
+
32
+ ### bcleanall
33
+
34
+ Walk all branches, and try to delete them if it's safe. See `bclean` for
35
+ details.
36
+
37
+ ### binfo
38
+
39
+ Verbose information about the current branch.
40
+
41
+ ### br
42
+
43
+ Verbose branch list. An alias for `git branch -v`.
44
+
45
+ ### feature
46
+
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.
51
+
52
+ ### forcepush, fpush
53
+
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.
58
+
59
+ ### lint
60
+
61
+ Run any linters configured in `.sugarjar.yaml`.
62
+
63
+ ### smartclone, sclone
64
+
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.
70
+
71
+ ### smartpush, spush
72
+
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.
75
+
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.
78
+
79
+ ### unit
80
+
81
+ Run any unitests configured in `.sugarjar.yaml`.
82
+
83
+ ### up
84
+
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`.
87
+
88
+ ### upall
89
+
90
+ Same as `up`, but for all branches.
91
+
92
+ ## User Configuration
93
+
94
+ Sugarjar will read in both a system-level config file
95
+ (`/etc/sugarjar/config.yaml`) and a user-level config file
96
+ `~/.config/sugarjar/config.yaml`, if they exist. Anything in the user config
97
+ will override the system config, and command-line options override both. The
98
+ yaml file is a straight key-value pair of options without their '--'. For
99
+ example:
100
+
101
+ ```yaml
102
+ debug: true
103
+ github-user: jaymzh
104
+ ```
105
+
106
+ In addition, the environment variable `SUGARJAR_DEBUG` can be defined to set
107
+ debug on. This is primarily used as a way to turn debug on earlier in order to
108
+ troubleshoot configuration parsing.
109
+
110
+ ## Repository Configuration
111
+
112
+ 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:
115
+
116
+ * lint - A list of scripts to run on `sj lint`. These should be linters like
117
+ rubocop or pyflake.
118
+ * unit - A list of scripts to run on `sj unit`. These should be unittest
119
+ runners like rspec or pyunit.
120
+ * on_push - A list of types (`lint`, `unit`) of checks to run before pushing.
121
+ It is highly recommended this is only `lint`. The goal here is to allow for
122
+ the user to get quick stylistic feedback before pushing their branch to avoid
123
+ the push-fix-push-fix loop.
124
+
125
+ Example configuration:
126
+
127
+ ```yaml
128
+ lint:
129
+ - scripts/lint
130
+ unit:
131
+ - scripts/unit
132
+ on_push:
133
+ - lint
134
+ ```
135
+
136
+ ## FAQ
137
+
138
+ Why the name SugarJar?
139
+
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.
145
+
146
+ Why did you use `hub` instead of the newer `gh` CLI?
147
+
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`.
@@ -0,0 +1,396 @@
1
+ require_relative 'util'
2
+ require_relative 'repoconfig'
3
+ require_relative 'log'
4
+ require_relative 'version'
5
+
6
+ class SugarJar
7
+ # This is the workhorse of SugarJar. Short of #initialize, all other public
8
+ # methods are "commands". Anything in private is internal implementation
9
+ # details.
10
+ class Commands
11
+ include SugarJar::Util
12
+
13
+ def initialize(options)
14
+ @ghuser = options['github_user']
15
+ @ghhost = options['github_host']
16
+ @repo_config = SugarJar::RepoConfig.config
17
+ set_hub_host if @ghhost
18
+ end
19
+
20
+ def feature(name, base = nil)
21
+ assert_in_repo
22
+ SugarJar::Log.debug("Feature: #{name}, #{base}")
23
+ die("#{name} already exists!") if all_branches.include?(name)
24
+ base ||= most_master
25
+ hub('checkout', '-b', name, base)
26
+ SugarJar::Log.info("Created feature branch #{name} based on #{base}")
27
+ end
28
+
29
+ def bclean(name = nil)
30
+ assert_in_repo
31
+ name ||= current_branch
32
+ # rubocop:disable Style/GuardClause
33
+ unless clean_branch(name)
34
+ die("Cannot clean #{name} - there are unmerged commits")
35
+ end
36
+ # rubocop:enable Style/GuardClause
37
+ end
38
+
39
+ def bcleanall
40
+ assert_in_repo
41
+ all_branches.each do |branch|
42
+ next if branch == 'master'
43
+
44
+ # rubocop:disable Style/Next
45
+ unless clean_branch(branch)
46
+ SugarJar::Log.info(
47
+ "Skipping branch #{branch} - there are unmerged commits"
48
+ )
49
+ end
50
+ # rubocop:enable Style/Next
51
+ end
52
+ end
53
+
54
+ def co(name)
55
+ assert_in_repo
56
+ hub('checkout', name)
57
+ end
58
+
59
+ def br
60
+ assert_in_repo
61
+ puts hub('branch', '-v').stdout
62
+ end
63
+
64
+ def binfo
65
+ assert_in_repo
66
+ SugarJar::Log.info(hub(
67
+ 'log', '--graph', '--oneline', '--decorate', '--boundary',
68
+ "#{tracked_branch}.."
69
+ ).stdout)
70
+ end
71
+
72
+ def up
73
+ assert_in_repo
74
+ result = gitup
75
+ if result
76
+ SugarJar::Log.info("Rebased branch on #{result}")
77
+ else
78
+ die('Failed to rebase current branch')
79
+ end
80
+ end
81
+
82
+ def amend(*args)
83
+ assert_in_repo
84
+ # This cannot use shellout since we need a full terminal for the editor
85
+ exit(system('/usr/bin/git', 'commit', '--amend', *args))
86
+ end
87
+
88
+ def qamend(*args)
89
+ assert_in_repo
90
+ SugarJar::Log.info(hub('commit', '--amend', '--no-edit', *args).stdout)
91
+ end
92
+
93
+ alias amendq qamend
94
+
95
+ def upall
96
+ assert_in_repo
97
+ all_branches.each do |branch|
98
+ next if branch == 'master'
99
+
100
+ hub('checkout', branch)
101
+ result = gitup
102
+ if result
103
+ SugarJar::Log.info("Rebased #{branch} on #{result}")
104
+ else
105
+ SugarJar::Log.error(
106
+ "Failed to rebase #{branch}, aborting that and moving to next " +
107
+ 'branch'
108
+ )
109
+ hub('rebase', '--abort')
110
+ end
111
+ end
112
+ end
113
+
114
+ def smartclone(repo, dir = nil, *args)
115
+ # If the user has specified a hub host, set the environment variable
116
+ # since we don't have a repo to configure yet
117
+ ENV['GITHUB_HOST'] = @ghhost if @ghhost
118
+
119
+ reponame = File.basename(repo, '.git')
120
+ dir ||= reponame
121
+ SugarJar::Log.info("Cloning #{reponame}...")
122
+ hub('clone', repo, dir, *args)
123
+
124
+ Dir.chdir dir do
125
+ # Now that we have a repo, if we have a hub host set it.
126
+ set_hub_host if @ghhost
127
+
128
+ org = File.basename(File.dirname(repo))
129
+ if org == @ghuser
130
+ put 'Cloned forked or self-owned repo. Not creating "upstream".'
131
+ return
132
+ end
133
+
134
+ s = hub_nofail('fork', '--remote-name=origin')
135
+ if s.error?
136
+ # if the fork command failed, we already have one, so we have
137
+ # to swap the remote names ourselves
138
+ SugarJar::Log.info("Fork (#{@ghuser}/#{reponame}) detected.")
139
+ hub('remote', 'rename', 'origin', 'upstream')
140
+ hub('remote', 'add', 'origin', repo.gsub("#{org}/", "#{@ghuser}/"))
141
+ else
142
+ SugarJar::Log.info("Forked #{reponame} to #{@ghuser}")
143
+ end
144
+ SugarJar::Log.info('Remotes "origin" and "upstream" configured.')
145
+ end
146
+ end
147
+
148
+ alias sclone smartclone
149
+
150
+ def lint
151
+ exit(1) unless run_check('lint')
152
+ end
153
+
154
+ def unit
155
+ exit(1) unless run_check('lint')
156
+ end
157
+
158
+ def smartpush(remote = nil, branch = nil)
159
+ unless remote && branch
160
+ remote ||= 'origin'
161
+ branch ||= current_branch
162
+ end
163
+
164
+ if run_prepush
165
+ puts hub('push', remote, branch).stderr
166
+ else
167
+ SugarJar::Log.error('Pre-push checks failed. Not pushing.')
168
+ end
169
+ end
170
+
171
+ alias spush smartpush
172
+
173
+ def forcepush(remote = nil, branch = nil)
174
+ unless remote && branch
175
+ remote ||= 'origin'
176
+ branch ||= current_branch
177
+ end
178
+ if run_prepush
179
+ puts hub('push', '--force-with-lease', remote, branch).stderr
180
+ else
181
+ SugarJar::Log.error('Pre-push checks failed. Not pushing.')
182
+ end
183
+ end
184
+
185
+ alias fpush forcepush
186
+
187
+ def version
188
+ puts "sugarjar version #{SugarJar::VERSION}"
189
+ puts hub('version').stdout
190
+ end
191
+
192
+ private
193
+
194
+ def set_hub_host
195
+ return unless in_repo
196
+
197
+ s = hub_nofail('config', '--local', '--get', 'hub.host')
198
+ if s.error?
199
+ SugarJar::Log.info("Setting repo hub.host = #{@ghhost}")
200
+ else
201
+ current = s.stdout
202
+ if current == @ghost
203
+ SugarJar::Log.debug('Repo hub.host already set correctly')
204
+ else
205
+ SugarJar::Log.info(
206
+ "Overwriting repo hub.host from #{current} to #{@ghhost}"
207
+ )
208
+ end
209
+ end
210
+ hub('config', '--local', '--add', 'hub.host', @ghhost)
211
+ end
212
+
213
+ def run_check(type)
214
+ unless @repo_config[type]
215
+ SugarJar::Log.debug("No #{type} configured. Returning success")
216
+ return true
217
+ end
218
+ Dir.chdir repo_root do
219
+ @repo_config[type].each do |check|
220
+ SugarJar::Log.info("Running #{type} #{check}")
221
+
222
+ unless File.exist?(check)
223
+ SugarJar::Log.error("Configured #{type} #{check} does not exist!")
224
+ return false
225
+ end
226
+ 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
231
+ end
232
+ end
233
+ end
234
+
235
+ def run_prepush
236
+ @repo_config['on_push'].each do |item|
237
+ SugarJar::Log.debug("Running on_push check type #{item}")
238
+ unless send(:run_check, item)
239
+ SugarJar::Log.info("Push check #{item} failed.")
240
+ return false
241
+ end
242
+ end
243
+ true
244
+ end
245
+
246
+ def die(msg)
247
+ SugarJar::Log.fatal(msg)
248
+ exit(1)
249
+ end
250
+
251
+ def assert_in_repo
252
+ die('sugarjar must be run from inside a git repo') unless in_repo
253
+ end
254
+
255
+ def clean_branch(name)
256
+ die('Cannot remove master branch') if name == 'master'
257
+ SugarJar::Log.debug('Fetch relevant remote...')
258
+ fetch_upstream
259
+ return false unless safe_to_clean(name)
260
+
261
+ SugarJar::Log.debug('branch deemed safe to delete...')
262
+ hub('checkout', 'master')
263
+ hub('branch', '-D', name)
264
+ gitup
265
+ SugarJar::Log.info("Reaped branch #{name}")
266
+ true
267
+ end
268
+
269
+ def all_branches
270
+ branches = []
271
+ hub('branch', '--format', '%(refname)').stdout.lines.each do |line|
272
+ next if line == 'master'
273
+
274
+ branches << line.strip.split('/')[2]
275
+ end
276
+ branches
277
+ end
278
+
279
+ def safe_to_clean(branch)
280
+ # cherry -v will output 1 line per commit on the target branch
281
+ # prefixed by a - or + - anything with a - can be dropped, anything
282
+ # else cannot.
283
+ out = hub(
284
+ 'cherry', '-v', tracked_branch, branch
285
+ ).stdout.lines.reject do |line|
286
+ line.start_with?('-')
287
+ end
288
+ if out.length.zero?
289
+ SugarJar::Log.debug(
290
+ "cherry-pick shows branch #{branch} obviously safe to delete"
291
+ )
292
+ return true
293
+ end
294
+
295
+ # if the "easy" check didn't work, it's probably because there
296
+ # was a squash-merge. To check for that we make our own squash
297
+ # merge to upstream/master and see if that has any delta
298
+
299
+ # First we need a temp branch to work on
300
+ tmpbranch = "_sugar_jar.#{Process.pid}"
301
+
302
+ hub('checkout', '-b', tmpbranch, tracked_branch)
303
+ s = hub_nofail('merge', '--squash', branch)
304
+ if s.error?
305
+ cleanup_tmp_branch(tmpbranch, branch)
306
+ error(
307
+ 'Failed to merge changes into current master. This means we could ' +
308
+ '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."
310
+ )
311
+ return false
312
+ end
313
+
314
+ s = hub('diff', '--staged')
315
+ out = s.stdout
316
+ SugarJar::Log.debug("Squash-merged diff: #{out}")
317
+ cleanup_tmp_branch(tmpbranch, branch)
318
+ if out.empty?
319
+ SugarJar::Log.debug(
320
+ 'After squash-merging, this branch appears safe to delete'
321
+ )
322
+ true
323
+ else
324
+ SugarJar::Log.debug(
325
+ 'After squash-merging, this branch is NOT fully merged to master'
326
+ )
327
+ false
328
+ end
329
+ end
330
+
331
+ def cleanup_tmp_branch(tmp, backto)
332
+ hub('reset', '--hard', tracked_branch)
333
+ hub('checkout', backto)
334
+ hub('branch', '-D', tmp)
335
+ end
336
+
337
+ def current_branch
338
+ hub('symbolic-ref', 'HEAD').stdout.strip.split('/')[2]
339
+ end
340
+
341
+ def fetch_upstream
342
+ us = upstream
343
+ hub('fetch', us) if us
344
+ end
345
+
346
+ def gitup
347
+ SugarJar::Log.debug('Fetching upstream')
348
+ fetch_upstream
349
+ SugarJar::Log.debug('Rebasing')
350
+ base = tracked_branch
351
+ s = hub_nofail('rebase', base)
352
+ s.error? ? nil : base
353
+ end
354
+
355
+ def tracked_branch
356
+ s = hub_nofail(
357
+ 'rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'
358
+ )
359
+ if s.error?
360
+ most_master
361
+ else
362
+ s.stdout.strip
363
+ end
364
+ end
365
+
366
+ def most_master
367
+ us = upstream
368
+ if us
369
+ "#{us}/master"
370
+ else
371
+ master
372
+ end
373
+ end
374
+
375
+ def upstream
376
+ return @remote if @remote
377
+
378
+ s = hub('remote')
379
+
380
+ remotes = s.stdout.lines.map(&:strip)
381
+ SugarJar::Log.debug("remotes is #{remotes}")
382
+ if remotes.empty?
383
+ @remote = nil
384
+ elsif remotes.length == 1
385
+ @remote = remotes[0]
386
+ elsif remotes.include?('upstream')
387
+ @remote = 'upstream'
388
+ elsif remotes.include?('origin')
389
+ @remote = 'origin'
390
+ else
391
+ raise 'Could not determine "upstream" remote to use...'
392
+ end
393
+ @remote
394
+ end
395
+ end
396
+ end
@@ -0,0 +1,31 @@
1
+ require 'yaml'
2
+ require_relative 'log'
3
+
4
+ class SugarJar
5
+ # This parses SugarJar configs (not to be confused with repoconfigs).
6
+ # This is stuff like log level, github-user, etc.
7
+ class Config
8
+ DEFAULTS = {
9
+ 'ghuser' => ENV['USER'],
10
+ 'fallthru' => true,
11
+ }.freeze
12
+
13
+ def self._find_ordered_files
14
+ [
15
+ '/etc/sugarjar/config.yaml',
16
+ "#{ENV['HOME']}/.config/sugarjar/config.yaml"
17
+ ].select { |f| File.exist?(f) }
18
+ end
19
+
20
+ def self.config
21
+ SugarJar::Log.debug("Defaults: #{DEFAULTS}")
22
+ c = DEFAULTS.dup
23
+ _find_ordered_files.each do |f|
24
+ SugarJar::Log.debug("Loading config #{f}")
25
+ c.merge!(YAML.safe_load(File.read(f)))
26
+ SugarJar::Log.debug("Modified config: #{c}")
27
+ end
28
+ c
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,24 @@
1
+ require 'mixlib/log'
2
+
3
+ module Mixlib
4
+ module Log
5
+ # A simple formatter so that 'info' is just like 'puts'
6
+ # but everything else gets a severity
7
+ class Formatter
8
+ def call(severity, _time, _progname, msg)
9
+ if severity == 'INFO'
10
+ "#{msg2str(msg)}\n"
11
+ else
12
+ "#{severity}: #{msg2str(msg)}\n"
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ class SugarJar
20
+ # Our singleton logger
21
+ class Log
22
+ extend Mixlib::Log
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'util'
2
+ require_relative 'log'
3
+ require 'yaml'
4
+
5
+ class SugarJar
6
+ # This parses SugarJar repoconfigs (not to be confused with configs).
7
+ # This is lint/unit/on_push configs.
8
+ class RepoConfig
9
+ extend SugarJar::Util
10
+
11
+ CONFIG_NAME = '.sugarjar.yaml'.freeze
12
+
13
+ def self.repo_config
14
+ ::File.join(repo_root, CONFIG_NAME)
15
+ end
16
+
17
+ def self.config
18
+ unless in_repo
19
+ SugarJar::Log.debug('Not in repo, skipping repoconfig load')
20
+ return {}
21
+ end
22
+ config = repo_config
23
+ if File.exist?(config)
24
+ SugarJar::Log.debug("Loading repo config: #{config}")
25
+ YAML.safe_load(File.read(repo_config))
26
+ else
27
+ SugarJar::Log.debug("No repo config (#{config}), returning empty hash")
28
+ {}
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,26 @@
1
+ require_relative 'log'
2
+
3
+ class SugarJar
4
+ # Some common methods needed by other classes
5
+ module Util
6
+ def hub_nofail(*args)
7
+ SugarJar::Log.trace("Running: hub #{args.join(' ')}")
8
+ Mixlib::ShellOut.new(['/usr/bin/hub'] + args).run_command
9
+ end
10
+
11
+ def hub(*args)
12
+ s = hub_nofail(*args)
13
+ s.error!
14
+ s
15
+ end
16
+
17
+ def in_repo
18
+ s = hub_nofail('rev-parse', '--is-inside-work-tree')
19
+ !s.error? && s.stdout.strip == 'true'
20
+ end
21
+
22
+ def repo_root
23
+ hub('rev-parse', '--show-toplevel').stdout.strip
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ class SugarJar
2
+ VERSION = '0.0.1'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sugarjar
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Phil Dibowitz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-06-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mixlib-log
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: mixlib-shellout
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
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
+ description:
98
+ email:
99
+ - phil@ipom.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files:
103
+ - README.md
104
+ - LICENSE
105
+ files:
106
+ - LICENSE
107
+ - README.md
108
+ - lib/sugarjar/commands.rb
109
+ - lib/sugarjar/config.rb
110
+ - lib/sugarjar/log.rb
111
+ - lib/sugarjar/repoconfig.rb
112
+ - lib/sugarjar/util.rb
113
+ - lib/sugarjar/version.rb
114
+ homepage: https://github.com/jaymzh/sugarjar
115
+ licenses:
116
+ - Apache-2.0
117
+ metadata: {}
118
+ post_install_message:
119
+ rdoc_options: []
120
+ require_paths:
121
+ - lib
122
+ required_ruby_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubygems_version: 3.0.3
134
+ signing_key:
135
+ specification_version: 4
136
+ summary: A git/github helper script
137
+ test_files: []