pax_global_header00006660000000000000000000000064145625636260014531gustar00rootroot0000000000000052 comment=0ba1e6618389a55c25b197c85e10a77ee8cdc50e sugarjar-1.1.1/000077500000000000000000000000001456256362600133475ustar00rootroot00000000000000sugarjar-1.1.1/.github/000077500000000000000000000000001456256362600147075ustar00rootroot00000000000000sugarjar-1.1.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001456256362600170725ustar00rootroot00000000000000sugarjar-1.1.1/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000007001456256362600215610ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: "[BUG]" labels: bug assignees: jaymzh --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior including commands and output **Expected behavior** A clear and concise description of what you expected to happen. **Environment (please complete the following information):** - OS: - Output of `sj version` sugarjar-1.1.1/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000012741456256362600226230ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: "[RFE]" labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Contribution** Are you willing to write this feature? If so, would you need assistance? **Additional context** Add any other context or screenshots about the feature request here. sugarjar-1.1.1/.github/ISSUE_TEMPLATE/support-request.md000066400000000000000000000005411456256362600226160ustar00rootroot00000000000000--- name: Support request about: Use this to ask for help title: "[support]" labels: '' assignees: '' --- **Describe the problem** Please describe the problem you are having in as much detail as possible. **What you've tried** Please describe what steps you've taken to try to solve the problem **Version** Please provide the output of `sj version` sugarjar-1.1.1/.github/workflows/000077500000000000000000000000001456256362600167445ustar00rootroot00000000000000sugarjar-1.1.1/.github/workflows/dco.yml000066400000000000000000000006221456256362600202340ustar00rootroot00000000000000name: DCO Check on: [pull_request] jobs: dco_check_job: runs-on: ubuntu-latest name: DCO Check steps: - name: Get PR Commits uses: tim-actions/get-pr-commits@master id: 'get-pr-commits' with: token: ${{ secrets.GITHUB_TOKEN }} - name: DCO Check uses: tim-actions/dco@master with: commits: ${{ steps.get-pr-commits.outputs.commits }} sugarjar-1.1.1/.github/workflows/lint.yml000066400000000000000000000017321456256362600204400ustar00rootroot00000000000000# rubocop action doesn't work yet: # https://github.com/gimenete/rubocop-action/issues/12 name: Lint on: push: branches: [ main ] pull_request: branches: [ main ] jobs: rubocop: strategy: fail-fast: false runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v4 - name: Setup ruby uses: ruby/setup-ruby@v1 with: ruby-version: '3.2' - name: install deps run: bundle install - name: Run rubocop run: bundle exec rubocop --display-cop-names # rubocop: # runs-on: ubuntu-latest # steps: # - name: checkout # uses: actions/checkout@v2 # - name: Rubocop checks # uses: gimenete/rubocop-action@1.0 # env: # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} markdownlint: runs-on: ubuntu-latest steps: - name: checkout uses: actions/checkout@v4 - name: MarkdownLint mdl Action uses: actionshub/markdownlint@1.2.0 sugarjar-1.1.1/.github/workflows/unit.yml000066400000000000000000000010051456256362600204420ustar00rootroot00000000000000name: Unittests on: push: branches: [ main ] pull_request: branches: [ main ] jobs: rspec: strategy: fail-fast: false matrix: ruby: [3.0, 3.1, 3.2, 3.3] runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - name: Install dependencies run: bundle install - name: Run rspec run: ./scripts/run_rspec.sh sugarjar-1.1.1/.gitignore000066400000000000000000000023361456256362600153430ustar00rootroot00000000000000*.gem *.rbc /.config /coverage/ /InstalledFiles /pkg/ /spec/reports/ /spec/examples.txt /test/tmp/ /test/version_tmp/ /tmp/ # Used by dotenv library to load environment variables. # .env # Ignore Byebug command history file. .byebug_history ## Specific to RubyMotion: .dat* .repl_history build/ *.bridgesupport build-iPhoneOS/ build-iPhoneSimulator/ ## Specific to RubyMotion (use of CocoaPods): # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # # vendor/Pods/ ## Documentation cache and generated files: /.yardoc/ /_yardoc/ /doc/ /rdoc/ ## Environment normalization: /.bundle/ /vendor/bundle /lib/bundler/man/ # for a library or gem, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # Gemfile.lock # .ruby-version # .ruby-gemset # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc # Used by RuboCop. Remote config files pulled in from inherit_from directive. # .rubocop-https?--* packaging/.vagrant noarch .ruby-version sugarjar-1.1.1/.mdl_style.rb000066400000000000000000000001521456256362600157440ustar00rootroot00000000000000all rule 'MD013', :ignore_code_blocks => true rule 'MD026', :punctuation => '.,:;' exclude_rule 'MD041' sugarjar-1.1.1/.mdlrc000066400000000000000000000000261456256362600144470ustar00rootroot00000000000000style '.mdl_style.rb' sugarjar-1.1.1/.rubocop.yml000066400000000000000000000021151456256362600156200ustar00rootroot00000000000000AllCops: TargetRubyVersion: 3.0 NewCops: enable Exclude: - 'omnibus/bin/**/*' - 'omnibus/local/**/*' - 'rubygem-sugarjar.spec' Layout/LineLength: Max: 80 Naming/FileName: Enabled: false Metrics/CyclomaticComplexity: Enabled: false Metrics/AbcSize: Enabled: false Metrics/ModuleLength: Enabled: false Metrics/MethodLength: Enabled: false Metrics/ClassLength: Enabled: false Metrics/BlockLength: Enabled: false Style/FrozenStringLiteralComment: EnforcedStyle: never Style/LineEndConcatenation: Enabled: false Style/StringConcatenation: Enabled: false Style/TrailingCommaInArrayLiteral: EnforcedStyleForMultiline: comma Style/TrailingCommaInHashLiteral: EnforcedStyleForMultiline: comma Style/HashSyntax: EnforcedStyle: hash_rockets Style/PercentLiteralDelimiters: PreferredDelimiters: default: '{}' '%i': '{}' '%I': '{}' '%w': '{}' '%W': '{}' '%r': '{}' Style/TrailingCommaInArguments: EnforcedStyleForMultiline: comma Metrics/PerceivedComplexity: Enabled: false Layout/DotPosition: EnforcedStyle: trailing sugarjar-1.1.1/.sugarjar.yaml000066400000000000000000000001121456256362600161210ustar00rootroot00000000000000on_push: [lint] lint_list_cmd: scripts/get_linters unit: - scripts/unit sugarjar-1.1.1/CHANGELOG.md000066400000000000000000000065031456256362600151640ustar00rootroot00000000000000# SugarJar Changelog ## 1.1.1 (2024-02-12) * Relax ruby requirements to allow for easier packaging * Handle aborted rebases better * Add bash-completion script * Various doc updates ## 1.1.0 (2023-12-31) * Fix include path for unittests for downstream packagers * Bump ruby min versions * Include Gemfile.lock for downstream packagers ## 1.0.1 (2023-12-20) * `co` support for featureprefix * Add `include_from` and `overwrite_from` support to repoconfig * Support relative paths for lints/units * `smartpr` now uses `--fill` ## 1.0.0 (2023-10-22) * Add new "feature prefix" feature * Implement `auto` setting for `github_cli`, default to `gh` * Point people to Sapling * Handle `sclone` of repos in personal orgs * Better error when a subcommand isn't specified * Various documentation fixes ## 0.0.11 (2022-10-06) * Properly handle slashes in branch names (closes #101) * Support for running a command to determine checks (linters, units) to run * Support for using `gh` CLI instead of `hub` (experimental) * Add new `pullsuggestions` command to pull in (accepted) suggestions from a GitHub code review. * Detect mismatched primary branch names to assist with projects changing from `master` to `main` ## 0.0.10 (2021-12-06) * Support 'main' as a default/primary branch * Fix doc errors * Handle rebase failures more gracefully, give users hints (closes #88) * Handle SAML errors better (closes #95) * Don't parse option args as subcommands (closes #89) ## 0.0.9 (2021-02-20) * Fix smartclone not honoring `--github-host` * Use SSH protocol by default on short repo names * Handle anonymous auth failures gracefully * Better support for autocorrecting linters ## 0.0.8 (2020-12-16) * Colorize and simplify output * New smartlog feature * Doc fixes ## 0.0.7 (2020-11-23) * Add new command `smartpullrequest` (or `smartpr` or `spr`) for creating pull requests (closes #51) * Add checks for dirty repos before `smartpush`, `forcepush`, and `smartpullrequest` * Add `--ignore-dirty` and `--ignore-prerun-failure` options * Handle when git prompts for a username (closes #52) * Always use SSH for the forked remote (closes #56) * Better handling of various forms of repo URLs * Fix typo of `version` in help message * Fix typos in `README.md` ## 0.0.6 (2020-07-05) * Add automatic commit template configuration (closes #38) * bcleanall: Return to reasonable branch (fixes #37) * Handle case where `hub` has no auth token (fixes #39) * Fix crash in `smartclone` * Improve logging * Fix `sj unit` running lints instead of units ## 0.0.5 (2020-06-24) * Fix global config file handling * Better logging around lint/unit failuers * Handle incorrect tracked branches better ## 0.0.4 (2020-06-17) * Fix gemspec to include executables * Add support for building omnibus releases ## 0.0.3 (2020-06-08) * Stop rescuing NoMethodError (fixing a variety of confusing error cases) * Fix crash when no `on_push` entry is in repo config * Document contribution process (`CONTRIBUTING.md`) * Document code of conduct (`CODE_OF_CONDUCT.md`) ## 0.0.2 (2020-06-06) * Fix 'co' not accepting multiple arguments/options * Fix README typos (#10, #11) * Don't assume the ruby to run under * Don't crash when no subcommands are passed in * Don't assume paths (e.g. for hub, git) * Fix crash for unknown method * fix handling of empty config files ## 0.0.1 (2020-06-05) * Initial release sugarjar-1.1.1/CODE_OF_CONDUCT.md000066400000000000000000000064241456256362600161540ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at phil@ipom.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available [here](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) For answers to common questions about this code of conduct, see [Contributor Covenant](https://www.contributor-covenant.org) sugarjar-1.1.1/CONTRIBUTING.md000066400000000000000000000015661456256362600156100ustar00rootroot00000000000000# Contributing to SugarJar We welcome contributions! Contributions come in a variety of forms: clear bug reports, code, or spreading the word about this project. If you'd like to contribute code, here's how. Simply use SugarJar to make a fork and setup your repo: ```shell sj sclone jaymzh/sugarjar ``` Make a branch for your change: ```shell sj feature mychange ``` Make whatever changes you want, commit with a clear commit message, and a DCO. We require [Developer Certificate of Origin (DCO)](https://developercertificate.org/) via a 'signed-off-by:` line in your commit (the `git commit -s` does this for you). The Chef community has a lot of great documentation on this which you can find [here](https://docs.chef.io/community_contributions/#developer-certification-of-origin-dco). ```shell git commit -as ``` Make a pull request: ```shell sj spush sj pull-request ``` sugarjar-1.1.1/Gemfile000066400000000000000000000001721456256362600146420ustar00rootroot00000000000000source 'https://rubygems.org' gem 'sugarjar', :path => '.' group :test do gem 'mdl' gem 'rspec' gem 'rubocop' end sugarjar-1.1.1/Gemfile.lock000066400000000000000000000036361456256362600156010ustar00rootroot00000000000000PATH remote: . specs: sugarjar (1.1.1) deep_merge mixlib-log mixlib-shellout pastel GEM remote: https://rubygems.org/ specs: ast (2.4.2) chef-utils (18.4.2) concurrent-ruby concurrent-ruby (1.2.3) deep_merge (1.2.2) diff-lcs (1.5.1) json (2.7.1) kramdown (2.4.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) language_server-protocol (3.17.0.3) mdl (0.13.0) kramdown (~> 2.3) kramdown-parser-gfm (~> 1.1) mixlib-cli (~> 2.1, >= 2.1.1) mixlib-config (>= 2.2.1, < 4) mixlib-shellout mixlib-cli (2.1.8) mixlib-config (3.0.27) tomlrb mixlib-log (3.0.9) mixlib-shellout (3.2.7) chef-utils parallel (1.24.0) parser (3.3.0.5) ast (~> 2.4.1) racc pastel (0.8.0) tty-color (~> 0.5) racc (1.7.3) rainbow (3.1.1) regexp_parser (2.9.0) rexml (3.2.6) rspec (3.13.0) rspec-core (~> 3.13.0) rspec-expectations (~> 3.13.0) rspec-mocks (~> 3.13.0) rspec-core (3.13.0) rspec-support (~> 3.13.0) rspec-expectations (3.13.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-mocks (3.13.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-support (3.13.0) rubocop (1.60.2) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) rubocop-ast (>= 1.30.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.30.0) parser (>= 3.2.1.0) ruby-progressbar (1.13.0) tomlrb (2.0.3) tty-color (0.6.0) unicode-display_width (2.5.0) PLATFORMS x86_64-darwin-23 x86_64-linux DEPENDENCIES mdl rspec rubocop sugarjar! BUNDLED WITH 2.5.3 sugarjar-1.1.1/LICENSE000066400000000000000000000261271456256362600143640ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020-present Phil Dibowitz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. sugarjar-1.1.1/README.md000066400000000000000000000370001456256362600146260ustar00rootroot00000000000000# SugarJar [![Lint](https://github.com/jaymzh/sugarjar/workflows/Lint/badge.svg)](https://github.com/jaymzh/sugarjar/actions?query=workflow%3ALint) [![Unittest](https://github.com/jaymzh/sugarjar/workflows/Unittests/badge.svg)](https://github.com/jaymzh/sugarjar/actions?query=workflow%3AUnittests) [![DCO](https://github.com/jaymzh/sugarjar/workflows/DCO%20Check/badge.svg)](https://github.com/jaymzh/sugarjar/actions?query=workflow%3A%22DCO+Check%22) > [!IMPORTANT] > As this was meant to replace arc/jf, which has now been open-sourced as > [Sapling](https://sapling-scm.com/), I highly recommend taking a look at that! > > Sapling is a great tool and solves a variety of problems SugarJar will never > be able to. However, it is a bigger workflow change, so existing SJ users > may choose to stick with this. Similarly some workflows may not be suitable > for Sapling. I still plan to maintain and develop SugarJar for the time being. Welcome to SugarJar - a git/github helper. It needs one of the GitHub CLI's: either [gh](https://cli.github.com/) or the older [hub](https://hub.github.com/). SugarJar is inspired by [arcanist](https://github.com/phacility/arcanist), and its replacement at Facebook, JellyFish. Many of the features they provide for the Phabricator workflow this aims to bring to the GitHub workflow. In particular there are a lot of helpers for using a squash-merge workflow that is poorly handled by the standard toolsets. If you miss Mondrian or Phabricator - this is the tool for you! If you don't, there's a ton of useful stuff for everyone! ## Installation Sugarjar is packaged in a variety of Linux distributions - see if it's on the list here, and if so, use your package manager (or `gem`) to install it: [![Packaging status](https://repology.org/badge/vertical-allrepos/sugarjar.svg?exclude_unsupported=1)](https://repology.org/project/sugarjar/versions) If you are using a Linux distribution version that is end-of-life'd, click the above image, it'll take you to a page that lists unsupported distro versions as well (they'll have older SugarJar, but they'll probably still have some version). Ubuntu users, Ubuntu versions prior to 24.x cannot be updated, so if you're on an older Ubuntu please use [this PPA](https://launchpad.net/~michel-slm/+archive/ubuntu/sugarjar) from our Ubuntu package maintainer. For MacOS users, we recommend using Homebrew - SugarJar is now in Homebrew Core. NOTE: If you previously used our custom Homebrew tap, you should remove and untap it: ```shell homebrew uninstall sugarjar homebrew untap jaymzh/sugarjar ``` Then you can install the core version (`brew install sugarjar`). Finally, if none of those work for you, you can clone this repo and run it directly from there. ## Auto cleanup squash-merged branches It is common for a PR to go back and forth with a variety of nits, lint fixes, typos, etc. that can muddy history. So many projects will "squash and merge" when they accept a pull request. However, that means `git branch -d ` doesn't work. Git will tell you the branch isn't fully merged. You can, of course `git branch -D `, but that does no safety checks at all, it forces the deletion. Enter `sj bclean` - it determines if the contents of your branch has been merge and safely deletes if so. ``` shell sj bclean ``` Will delete a branch, if it has been merged, **even if it was squash-merged**. You can pass it a branch if you'd like (it defaults to the branch you're on): `sj bclean `. But it gets better! You can use `sj bcleanall` to remove all branches that have been merged: ```shell $ git branch * argparse master feature hubhost $ git bcleanall Skipping branch argparse - there are unmerged commits Reaped branch feature Reaped branch hubhost ``` ## Smarter clones and remotes There's a pattern to every new repo we want to contribute to. First we fork, then we clone the fork, then we add a remote of the upstream repo. It's monotonous. SugarJar does this for you: ```shell sj smartclone jaymzh/sugarjar ``` (also `sj sclone`) This will: * Make a fork of the repo, if you don't already have one * Clone your fork * Add the original as an 'upstream' remote Note that it takes short names for repos. No need to specify a full URL, just a $org/$repo. Like `git clone`, `sj sclone` will accept an additional argument as the destination directory to clone to. It will also pass any other unknown options to `git clone` under the hood. ## Work with stacked branches more easily It's important to break changes into reviewable chunks, but working with stacked branches can be confusing. Enter `binfo` - it gives you a view of your current branch all the way up to master. In this example imagine we have a branch structure like: ```text +- test2.1 / master --- test --- test2 --- test3 ``` This is what `binfo` on test3 looks like: ```shell $ sj binfo * e451865 (HEAD -> test3) test3 * e545b41 (test2) test2 * c808eae (test1) test1 o 44cf9e2 (origin/master, origin/HEAD, master) Lint/gemspec cleanups ``` while `binfo` on test2.1 looks like: ```shell $ sj binfo * 36d0136 (HEAD -> test2.1) test2.1 * e545b41 (test2) test2 * c808eae (test1) test1 o 44cf9e2 (origin/master, origin/HEAD, master) Lint/gemspec cleanups ``` ## Have a better lint/unittest experience! Ever made a PR, only to find out later that it failed tests because of some small lint issue? Not anymore! SJ can be configured to run things before pushing. For example,in the SugarJar repo, we have it run Rubocop (ruby lint) and Markdownlint "on_push". If those fail, it lets you know and doesn't push. You can configure SugarJar to tell it how to run both lints and unittests for a given repo and if one or both should be run prior to pushing. The details on the config file format is below, but we provide three commands: ```shell git lint ``` Run all linters. ```shell git unit ``` Run all unittests. ```shell git smartpush # or spush ``` Run configured push-time actions (nothing, lint, unit, both), and do not push if any of them fail. ## Better push defaults In addition to running pre-push tests for you `smartpush` also picks smart defaults for push. So if you `sj spush` with no arguments, it uses the `origin` remote and the same branch name you're on as the remote branch. ## Cleaning up your own history Perhaps you contribute to a project that prefers to use merge commits, so you like to clean up your own history. This is often difficult to get right - a combination of rebases, amends and force pushes. We provide two commands here to help. The first is pretty straight forward and is basically just an alias: `sj amend`. It will amend whatever you want to the most recent commit (just an alias for `git commit --amend`). It has a partner `qamend` (or `amendq` if you prefer) that will do so without prompting to update your commit message. So now you've rebased or amended, pushing becomes challenging. You can `git push --force`, but everyone knows that's incredibly dangerous. Is there a better way? There is! Git provides `git push --force-with-lease` - it checks to make sure you're up-to-date with the remote before forcing the push. But man that command is a mouthful! Enter `sj fpush`. It has all the smarts of `sj smartpush` (runs configured pre-push actions), but adds `--force-with-lease` to the command! ## Better feature branches When you want to start a new feature, you want to start developing against latest. That's why `sj feature` defaults to creating a branch against what we call "most master". That is, `upstream/master` if it exists, otherwise `origin/master` if that exists, otherwise `master`. You can pass in an additional argument to base it off of something else. ```shell $ git branch master test1 test2 * test2.1 test3 $ sj feature test-branch Created feature branch test-branch based on origin/master $ sj feature dependent-feature test-branch Created feature branch dependent-feature based on test-branch ``` Additionally you can specify a `feature_prefix` in your config which will cause `feature` to create branches prefixed with your `feature_prefix` and will also cause `co` to checkout branches with that prefix. This is useful when organizations use branch-based workflows and branches need to be prefixed with e.g. `$USER/`. For example, if your prefix was `user/`, then `sj feature foo` would create `user/foo`, and `sj co foo` would switch to `user/foo`. ## Smartlog Smartlog will show you a tree diagram of your branches! Simply run `sj smartlog` or `sj sl` for short. ![smartlog screenshot](https://github.com/jaymzh/sugarjar/blob/main/smartlog.png) ## Pulling in suggestions from the web When someone 'suggests' a change in the GitHub WebUI, once you choose to commit them, your origin and local branches are no longer in-sync. The `pullsuggestions` command will attempt to merge in any remote commits to your local branch. This command will show a diff and ask for confirmation before attempting the merge and - if allowed to continue - will use a fast-forward merge. ## And more! See `sj help` for more commands! ## Using SugarJar as a git wrapper SugarJar, by default, will pass any command it doesn't know straight to `hub` (which passes commands **it** doesn't know to `git`). If you have configured SugarJar to use `gh` instead of `hub`, then it will pass commands straight to `git` since `gh` doesn't act as a `git` wrapper. As such you can alias it to `git` and just have a super-git. ```shell $ alias git=sj $ git config -l | grep color color.diff=auto color.status=auto color.branch=auto color.branch.current=yellow reverse color.branch.local=yellow color.branch.remote=green $ git br * dependent-feature 44cf9e2 Lint/gemspec cleanups master 44cf9e2 Lint/gemspec cleanups test-branch 44cf9e2 Lint/gemspec cleanups test1 c808eae [ahead 1] test1 test2 e545b41 test2 test2.1 c1831b3 test2.1 test3 e451865 test3 ``` It's for this reason that SugarJar doesn't have conflicting command names. You can turn off fallthru by setting `fallthru: false` in your config. The only command we "override" is `version`, in which case we not only print our version, but also call `hub version` which prints its version and calls `git version` too! ## Configuration Sugarjar will read in both a system-level config file (`/etc/sugarjar/config.yaml`) and a user-level config file `~/.config/sugarjar/config.yaml`, if they exist. Anything in the user config will override the system config, and command-line options override both. The yaml file is a straight key-value pair of options without their '--'. For example: ```yaml log_level: debug github_user: jaymzh ``` In addition, the environment variable `SUGARJAR_LOGLEVEL` can be defined to set a log level. This is primarily used as a way to turn debug on earlier in order to troubleshoot configuration parsing. ## Repository Configuration Sugarjar looks for a `.sugarjar.yaml` in the root of the repository to tell it how to handle repo-specific things. Currently there options are: * `lint` - A list of scripts to run on `sj lint`. These should be linters like rubocop or pyflake. Linters will be run from the root of the repo. * `lint_list_cmd` - A command to run which will print out linters to run, one per line. Takes precedence over `lint`. The command (and the resulting linters) will be run from the root of the repo. * `unit` - A list of scripts to run on `sj unit`. These should be unittest runners like rspec or pyunit. Test will be run from the root of the repo. * `unit_list_cmd` - A command to run which will print out the unit tests to run, one more line. Takes precedence over `unit`. The command (and the resulting unit tests) will be run from the root of the repo. * `on_push` - A list of types (`lint`, `unit`) of checks to run before pushing. It is highly recommended this is only `lint`. The goal here is to allow for the user to get quick stylistic feedback before pushing their branch to avoid the push-fix-push-fix loop. * `commit_template` - A path to a commit template to set in the `commit.template` git config for this repo. Should be either a fully-qualified path, or a path relative to the repo root. * `include_from` - This will read an additional repoconfig file and merge it into the one being read. The value should be relative to the root of the repo. This will not error if the file does not exist, it is intended for organizations to allow users to optionally extend a default repo config. * `overwrite_from` - Same as `include_from`, but completely overwrites the base configuration if the file is found. Example configuration: ```yaml lint: - scripts/lint unit: - scripts/unit on_push: - lint commit_template: .commit-template.txt ``` ### Commit Templates While GitHub provides a way to specify a pull-request template by putting the right file into a repo, there is no way to tell git to automatically pick up a commit template by dropping a file in the repo. Users must do something like: `git config commit.template `. Making each developer do this is error prone, so this setting will automatically set this up for each developer. ## Enterprise GitHub Like `hub`, SugarJar supports GitHub Enterprise. In fact, we provide extra features just for it. We recommend the global or user config specify the `github_host`. However, most users will also have a few repos from upstream so always specifying a `github_host` is sub-optimal. So, when you overwrite the `github_host` on the command line, we go ahead and set the `hub.host` git config in that single repo so that it'll "just work" from there on out. In other words, assuming your global SJ config has `github_host: github.sample.com`, and the you clone sugarjar with: ```shell sj clone jaymzh/sugarjar --github-host githuh.com ``` We will add the `hub.host` to the `sugarjar` clone so that future `hub` or `sj` commands work without needing to specify.. ## Choosing a GitHub CLI SugarJar will use `gh` if it is available or otherwise fall back to `hub`. You can override this by specifying `--github-cli` on the command line or setting `github_cli` to either `gh` or `hub` (it defaults to `auto`) in your configuration. ## FAQ **Why the name SugarJar?** It's mostly a backronym. Like jellyfish, I wanted two letters that were on home row on different sides of the keyboard to make it easy to type. I looked at the possible options that where there and not taken and tried to find one I could make an appropriate name out of. Since this utility adds lots of sugar to git and github, it seemed appropriate. **Why did you originally use `hub` instead of the newer `gh` CLI?** When I originally wrote SugarJar, `gh` was in early development, and `hub` had many more features. In addition, I wrote SugarJar to be a wrapper for git/hub, and `hub` allows this but `gh` does not. When `gh` matured, we added experimental `gh` support in 0.0.11, and switched the default to prefer `gh` in 1.0.0. **I'd like to package SugarJar for my favorite distro/OS, is that OK?** Of course! But I'd appreciate you emailing me to give me a heads up. Doing so will allow me to make sure it shows up in the Repology badge above. **What platforms does it work on?** Since it's Ruby, it should work across all platforms, however, it's developed and primarily tested on Linux as well as regularly used on Mac. I've not tested it on Windows, but I'll happily accept patches for Windows compatibility. **How do I get tab-completion?** If the package for your OS/distro didn't set it up manually, you should find that `sugarjar_completion.bash` is included in the package, and you can simply source that in your dotfiles, assuming you are using bash. sugarjar-1.1.1/RELEASE_PROCESS.md000066400000000000000000000014451456256362600161530ustar00rootroot00000000000000# Rolling a release ## Optionally, update Gemfile.lock * Update gems with `bundle update --all` * Test to make sure we work with all new deps ## Prep the release * Update version number in `lib/sugarjar/version.rb` * Update the `CHANGELOG.md` * Create a PR, get it merged ## Tag the release * version='0.0.X' * Add a tag: `git tag -a v${version?} -m "version ${version?}" -s` * Push the tag: `git push origin --tags` ## Publish a gem * Build a gem: `gem build sugarjar.gemspec` * Push the gem: `gem push sugarjar-${version?}.gem` ## Publish Fedora builds See [packaging/README.md](packaging/README.md). ## Notify Debian/Ubuntu packager Ping Michel Lind ## Update Homebrew Open a PR against the [Homebrew Formula](https://github.com/Homebrew/homebrew-core/blob/master/Formula/s/sugarjar.rb). sugarjar-1.1.1/bin/000077500000000000000000000000001456256362600141175ustar00rootroot00000000000000sugarjar-1.1.1/bin/sj000077500000000000000000000243541456256362600144710ustar00rootroot00000000000000#!/usr/bin/env ruby # SugarJar require 'optparse' require 'mixlib/shellout' require_relative '../lib/sugarjar/commands' require_relative '../lib/sugarjar/config' require_relative '../lib/sugarjar/log' require_relative '../lib/sugarjar/util' require_relative '../lib/sugarjar/version' SugarJar::Log.level = Logger::INFO # Don't put defaults here, put them in SugarJar::Config - otherwise # these defaults overwrite whatever is in config files. options = { 'color' => true } # If ENV['SUGARJAR_DEBUG'] is set, it overrides the config file, # but not the command line options, so set that one here. Also # start the logger at that level, in case we are debugging option loading # itself if ENV['SUGARJAR_LOGLEVEL'] options['log_level'] = SugarJar::Log.level = ENV['SUGARJAR_LOGLEVEL'].to_sym end parser = OptionParser.new do |opts| opts.banner = 'Usage: sj [] []' opts.separator '' opts.separator 'Command, args, and options, can appear in any order.' opts.separator '' opts.separator 'OPTIONS:' opts.on('--[no-]fallthru', 'Fall-thru to git. [default: true]') do |fallthru| options['fallthru'] = fallthru end opts.on('--feature-prefix', 'Prefix to use for feature branches') do |prefix| options['feature_prefix'] = prefix end opts.on( '--github-cli CLI', %w{gh cli}, 'Github CLI to use ("gh" or "hub" or "auto"). Auto (the default) will ' + 'prefer "gh" if it is available but will fall back to "hub." ' + '[default: "auto"]', ) do |cli| options['github_cli'] = cli end opts.on( '--github-host HOST', 'The host for "hub". Note that we will set this in the local repo ' + 'config so there is no need to have multiple config files for multiple ' + 'github servers. Put your default one in your config file, and simply ' + 'specify this option the first time you clone or touch a repo and it ' + 'will be part of that repo until changed.', ) do |host| options['github_host'] = host end opts.on('--github-user USER', 'Github username') do |user| options['github_user'] = user end opts.on('-h', '--help', 'Print this help message') do puts opts exit end opts.on( '--ignore-dirty', 'Tell command that check for a dirty repo to carry on anyway. ' + '[default: false]', ) do options['ignore_dirty'] = true end opts.on( '--ignore-prerun-failure', 'Ignore preprun failure on *push commands. [default: false]', ) do options['ignore_prerun_failure'] = true end opts.on( '--log-level LEVEL', 'Set logging level (fatal, error, warning, info, debug, trace). This can ' + 'also be set via the SUGARJAR_LOGLEVEL environment variable. [default: ' + 'info]', ) do |level| options['log_level'] = level end opts.on('--[no-]use-color', 'Enable color. [default: true]') do |color| options['color'] = color end opts.on('--version') do puts SugarJar::VERSION exit end # rubocop:disable Layout/HeredocIndentation opts.separator < true })) extra_opts = [] # as with above, this can't go into 'options', until after we parse # the command line args config = SugarJar::Config.config valid_commands = sj.public_methods - Object.public_methods possible_valid_command = ARGV.any? do |arg| valid_commands.include?(arg.to_s.to_sym) end # if we're configured to fall thru and the subcommand isn't one # we recognize, don't parse the options as they may be different # than git's. For example `git config -l` - we error because we # require an arguement to `-l`. if config['fallthru'] && !possible_valid_command SugarJar::Log.debug( 'Skipping option parsing: fall-thru is set and we do not recognize ' + 'any subcommands', ) else SugarJar::Log.debug( 'We MIGHT have a valid command... parse-command line options', ) # We want to allow people to pass in extra args to be passed to # git commands, but OptionParser doesn't easily allow this. So we # loop over it, catching exceptions. begin # HOWEVER, anytime it throws an exception, for some reason, it clears # out all of ARGV, or whatever you passed to as ARGV. # # This not only prevents further parsing, but also means we lose # any non-option arguements (like the subcommand!) # # So we save a copy, and if we throw an exception, save the option that # caused it, remove that option from our copy, and then re-populate argv # with what's left. # # By doing this we not only get to parse all the options properly and # save unknown ones, but non-option arguements, which OptionParser # normally leaves in ARGV stay in ARGV. saved_argv = argv_copy.dup parser.parse!(argv_copy) rescue OptionParser::InvalidOption => e SugarJar::Log.debug("Saving unknown argument #{e.args}") extra_opts += e.args # e.args is an array, but it's only ever one arguement per exception saved_argv.delete(e.args.first) argv_copy = saved_argv.dup SugarJar::Log.debug( "Continuing option parsing with remaining ARGV: #{argv_copy}", ) retry end end subcommand = argv_copy.reject { |x| x.start_with?('-') }.first if ARGV.empty? || !subcommand puts parser exit end options = config.merge(options) # Recreate SJ with all of our options SugarJar::Log.level = options['log_level'].to_sym if options['log_level'] sj = SugarJar::Commands.new(options) is_valid_command = valid_commands.include?(subcommand.to_sym) argv_copy.delete(subcommand) SugarJar::Log.debug("subcommand is #{subcommand}") # Extra options we got, plus any left over arguements are what we # pass to Commands so they can be passed to git as necessary extra_opts += argv_copy SugarJar::Log.debug("extra unknown options: #{extra_opts}") if subcommand == 'help' puts parser exit end if is_valid_command SugarJar::Log.debug( "running #{subcommand}; extra opts: #{extra_opts.join(', ')}", ) sj.send(subcommand.to_sym, *extra_opts) elsif options['fallthru'] SugarJar::Log.debug("Falling thru to: hub #{ARGV.join(' ')}") if options['github_cli'] == 'hub' exec('hub', *ARGV) else # If we're using 'gh', it doesn't have 'git fall thru' support, so # we pass thru directly to 'git' exec('git', *ARGV) end else SugarJar::Log.error("No such subcommand: #{subcommand}") end sugarjar-1.1.1/extras/000077500000000000000000000000001456256362600146555ustar00rootroot00000000000000sugarjar-1.1.1/extras/sugarjar_completion.bash000066400000000000000000000022431456256362600215640ustar00rootroot00000000000000# bash completion for sugarjar SJCONFIG="$HOME/.config/sugarjar/config.yaml" _sugarjar_completions() { if [ "${#COMP_WORDS[@]}" -eq 2 ]; then return fi local -a suggestions # grap the feature_prefix if we have one so that we # can let the user ignore that part. If we have `yq` # we'll use it as that's going to be always 100% # reliable, but if we don't, do our best with shell # utils local prefix='' if [ -e "$SJCONFIG" ]; then if type yq &>/dev/null; then prefix=$(yq .feature_prefix $SJCONFIG) else # the xargs removes extra spaces prefix=$(grep feature_prefix $SJCONFIG | cut -f2 -d: | xargs) fi fi case "${COMP_WORDS[1]}" in co|checkout|bclean) local branches=$(git branch | sed -e 's/* //g' | xargs) if [ -n "$prefix" ]; then local branches=$(echo $branches | sed -e "s!$prefix!!g") fi suggestions=($(compgen -W "$branches" -- "${COMP_WORDS[2]}")) COMPREPLY=("${suggestions[@]}") ;; *) return esac } complete -F _sugarjar_completions sj sugarjar-1.1.1/lib/000077500000000000000000000000001456256362600141155ustar00rootroot00000000000000sugarjar-1.1.1/lib/sugarjar/000077500000000000000000000000001456256362600157335ustar00rootroot00000000000000sugarjar-1.1.1/lib/sugarjar/commands.rb000066400000000000000000000661441456256362600200740ustar00rootroot00000000000000require 'mixlib/shellout' require_relative 'util' require_relative 'repoconfig' require_relative 'log' require_relative 'version' class SugarJar # This is the workhorse of SugarJar. Short of #initialize, all other public # methods are "commands". Anything in private is internal implementation # details. class Commands include SugarJar::Util MAIN_BRANCHES = %w{master main}.freeze def initialize(options) SugarJar::Log.debug("Commands.initialize options: #{options}") @ghuser = options['github_user'] @ghhost = options['github_host'] @ignore_dirty = options['ignore_dirty'] @ignore_prerun_failure = options['ignore_prerun_failure'] @repo_config = SugarJar::RepoConfig.config SugarJar::Log.debug("Repoconfig: #{@repo_config}") @color = options['color'] @feature_prefix = options['feature_prefix'] @checks = {} @main_branch = nil @main_remote_branches = {} return if options['no_change'] # technically this doesn't "change" things, but we won't have this # option on the no_change call @cli = determine_cli(options['github_cli']) set_hub_host set_commit_template if @repo_config['commit_template'] end def feature(name, base = nil) assert_in_repo SugarJar::Log.debug("Feature: #{name}, #{base}") name = fprefix(name) die("#{name} already exists!") if all_local_branches.include?(name) base ||= most_main base_pieces = base.split('/') git('fetch', base_pieces[0]) if base_pieces.length > 1 git('checkout', '-b', name, base) SugarJar::Log.info( "Created feature branch #{color(name, :green)} based on " + color(base, :green), ) end def bclean(name = nil) assert_in_repo name ||= current_branch name = fprefix(name) unless all_local_branches.include?(name) if clean_branch(name) SugarJar::Log.info("#{name}: #{color('reaped', :green)}") else die( "#{color("Cannot clean #{name}", :red)}! there are unmerged " + "commits; use 'git branch -D #{name}' to forcefully delete it.", ) end end def bcleanall assert_in_repo curr = current_branch all_local_branches.each do |branch| if MAIN_BRANCHES.include?(branch) SugarJar::Log.debug("Skipping #{branch}") next end if clean_branch(branch) SugarJar::Log.info("#{branch}: #{color('reaped', :green)}") else SugarJar::Log.info("#{branch}: skipped") SugarJar::Log.debug( "There are unmerged commits; use 'git branch -D #{branch}' to " + 'forcefully delete it)', ) end end # Return to the branch we were on, or main if all_local_branches.include?(curr) git('checkout', curr) else checkout_main_branch end end def co(*args) assert_in_repo # Pop the last arguement, which is _probably_ a branch name # and then add any featureprefix, and if _that_ is a branch # name, replace the last arguement with that name = args.last bname = fprefix(name) unless all_local_branches.include?(name) if all_local_branches.include?(bname) SugarJar::Log.debug("Featurepefixing #{name} -> #{bname}") args[-1] = bname end s = git('checkout', *args) SugarJar::Log.info(s.stderr + s.stdout.chomp) end def br assert_in_repo SugarJar::Log.info(git('branch', '-v').stdout.chomp) end def binfo assert_in_repo SugarJar::Log.info(git( 'log', '--graph', '--oneline', '--decorate', '--boundary', "#{tracked_branch}.." ).stdout.chomp) end # binfo for all branches def smartlog assert_in_repo SugarJar::Log.info(git( 'log', '--graph', '--oneline', '--decorate', '--boundary', '--branches', "#{most_main}.." ).stdout.chomp) end alias sl smartlog def up assert_in_repo # get a copy of our current branch, if rebase fails, we won't # be able to determine it without backing out curr = current_branch result = gitup if result['so'].error? backout = '' if rebase_in_progress? backout = ' You can get out of this with a `git rebase --abort`.' end die( "#{color(curr, :red)}: Failed to rebase on " + "#{result['base']}. Leaving the repo as-is.#{backout} " + 'Output from failed rebase is: ' + "\nSTDOUT:\n#{result['so'].stdout.lines.map { |x| "\t#{x}" }.join}" + "\nSTDERR:\n#{result['so'].stderr.lines.map { |x| "\t#{x}" }.join}", ) else SugarJar::Log.info( "#{color(current_branch, :green)} rebased on #{result['base']}", ) end end def amend(*args) assert_in_repo # This cannot use shellout since we need a full terminal for the editor exit(system(which('git'), 'commit', '--amend', *args)) end def qamend(*args) assert_in_repo SugarJar::Log.info(git('commit', '--amend', '--no-edit', *args).stdout) end alias amendq qamend def upall assert_in_repo all_local_branches.each do |branch| next if MAIN_BRANCHES.include?(branch) git('checkout', branch) result = gitup if result['so'].error? SugarJar::Log.error( "#{color(branch, :red)} failed rebase. Reverting attempt and " + 'moving to next branch. Try `sj up` manually on that branch.', ) git('rebase', '--abort') if rebase_in_progress? else SugarJar::Log.info( "#{color(branch, :green)} rebased on " + color(result['base'], :green).to_s, ) end end end def smartclone(repo, dir = nil, *args) # If the user has specified a hub host, set the environment variable # since we don't have a repo to configure yet ENV['GITHUB_HOST'] = @ghhost if @ghhost reponame = File.basename(repo, '.git') dir ||= reponame org = extract_org(repo) SugarJar::Log.info("Cloning #{reponame}...") # GH's 'fork' command (with the --clone arg) will fork, if necessary, # then clone, and then setup the remotes with the appropriate names. So # we just let it do all the work for us and return. # # Unless the repo is in our own org and cannot be forked, then it # will fail. if gh? && org != @ghuser ghcli('repo', 'fork', '--clone', canonicalize_repo(repo), dir, *args) SugarJar::Log.info('Remotes "origin" and "upstream" configured.') return end # For 'hub' first we clone, using git, as 'hub' always needs a repo to # operate on. # # Or for 'gh' when we can't fork... git('clone', canonicalize_repo(repo), dir, *args) # Then we go into it and attempt to use the 'fork' capability # or if not Dir.chdir dir do # Now that we have a repo, if we have a hub host set it. set_hub_host SugarJar::Log.debug("Comparing org #{org} to ghuser #{@ghuser}") if org == @ghuser puts 'Cloned forked or self-owned repo. Not creating "upstream".' SugarJar::Log.info('Remotes "origin" and "upstream" configured.') return end s = ghcli_nofail('repo', 'fork', '--remote-name=origin') if s.error? if s.stdout.include?('SAML enforcement') SugarJar::Log.info( 'Forking the repo failed because the repo requires SAML ' + "authentication. Full output:\n\n\t#{s.stdout}", ) exit(1) else # gh as well as old versions of hub, it would fail if the upstream # fork already existed. If we got an error, but didn't recognize # that, we'll assume that's what happened and try to add the remote # ourselves. SugarJar::Log.info("Fork (#{@ghuser}/#{reponame}) detected.") SugarJar::Log.debug( 'The above is a bit of a lie. "hub" failed to fork and it was ' + 'not a SAML error, so our best guess is that a fork exists ' + 'and so we will try to configure it.', ) git('remote', 'rename', 'origin', 'upstream') git('remote', 'add', 'origin', forked_repo(repo, @ghuser)) end else SugarJar::Log.info("Forked #{reponame} to #{@ghuser}") end SugarJar::Log.info('Remotes "origin" and "upstream" configured.') end end alias sclone smartclone def lint assert_in_repo exit(1) unless run_check('lint') end def unit assert_in_repo exit(1) unless run_check('unit') end def smartpush(remote = nil, branch = nil) assert_in_repo _smartpush(remote, branch, false) end alias spush smartpush def forcepush(remote = nil, branch = nil) assert_in_repo _smartpush(remote, branch, true) end alias fpush forcepush def version puts "sugarjar version #{SugarJar::VERSION}" puts ghcli('version').stdout # 'hub' prints the 'git' version, but gh doesn't, so if we're on 'gh' # print out the git version directly puts git('version').stdout if gh? end def smartpullrequest(*args) assert_in_repo assert_common_main_branch if dirty? SugarJar::Log.warn( 'Your repo is dirty, so I am not going to create a pull request. ' + 'You should commit or amend and push it to your remote first.', ) exit(1) end if gh? SugarJar::Log.trace("Running: gh pr create #{args.join(' ')}") system(which('gh'), 'pr', 'create', '--fill', *args) else SugarJar::Log.trace("Running: hub pull-request #{args.join(' ')}") system(which('hub'), 'pull-request', *args) end end alias spr smartpullrequest alias smartpr smartpullrequest def pullsuggestions assert_in_repo if dirty? if @ignore_dirty SugarJar::Log.warn( 'Your repo is dirty, but --ignore-dirty was specified, so ' + 'carrying on anyway.', ) else SugarJar::Log.error( 'Your repo is dirty, so I am not going to push. Please commit ' + 'or amend first.', ) exit(1) end end src = "origin/#{current_branch}" fetch('origin') diff = git('diff', src).stdout return unless diff && !diff.empty? puts "Will merge the following suggestions:\n\n#{diff}" loop do $stdout.print("\nAre you sure? [y/n] ") ans = $stdin.gets.strip case ans when /^[Yy]$/ system(which('git'), 'merge', '--ff', "origin/#{current_branch}") break when /^[Nn]$/, /^[Qq](uit)?/ puts 'Not merging at user request...' break else puts "Didn't understand '#{ans}'." end end end alias ps pullsuggestions private def fprefix(name) return name unless @feature_prefix newname = "#{@feature_prefix}#{name}" SugarJar::Log.debug( "Munging feature name: #{name} -> #{newname} due to feature prefix", ) newname end def _smartpush(remote, branch, force) unless remote && branch remote ||= 'origin' branch ||= current_branch end if dirty? if @ignore_dirty SugarJar::Log.warn( 'Your repo is dirty, but --ignore-dirty was specified, so ' + 'carrying on anyway.', ) else SugarJar::Log.error( 'Your repo is dirty, so I am not going to push. Please commit ' + 'or amend first.', ) exit(1) end end unless run_prepush if @ignore_prerun_failure SugarJar::Log.warn( 'Pre-push checks failed, but --ignore-prerun-failure was ' + 'specified, so carrying on anyway', ) else SugarJar::Log.error('Pre-push checks failed. Not pushing.') exit(1) end end args = ['push', remote, branch] args << '--force-with-lease' if force puts git(*args).stderr end def dirty? s = git_nofail('diff', '--quiet') s.error? end def extract_org(repo) if repo.start_with?('http') File.basename(File.dirname(repo)) elsif repo.start_with?('git@') repo.split(':')[1].split('/')[0] else # assume they passed in a hub-friendly name repo.split('/').first end end def forked_repo(repo, username) repo = if repo.start_with?('http', 'git@') File.basename(repo) else "#{File.basename(repo)}.git" end "git@#{@ghhost || 'github.com'}:#{username}/#{repo}" end # Hub will default to https, but we should always default to SSH # unless otherwise specified since https will cause prompting. def canonicalize_repo(repo) # if they fully-qualified it, we're good return repo if repo.start_with?('http', 'git@') # otherwise, ti's a shortname cr = "git@#{@ghhost || 'github.com'}:#{repo}.git" SugarJar::Log.debug("canonicalized #{repo} to #{cr}") cr end def set_hub_host return unless hub? && in_repo && @ghhost s = git_nofail('config', '--local', '--get', 'hub.host') if s.error? SugarJar::Log.info("Setting repo hub.host = #{@ghhost}") else current = s.stdout if current == @ghhost SugarJar::Log.debug('Repo hub.host already set correctly') else # Even though we have an explicit config, in most cases, it # comes from a global or user config, but the config in the # local repo we likely set. So we'd just constantly revert that. SugarJar::Log.debug( "Not overwriting repo hub.host. Already set to #{current}. " + "To change it, run `git config --local --add hub.host #{@ghhost}`", ) end return end git('config', '--local', '--add', 'hub.host', @ghhost) end def set_commit_template unless in_repo SugarJar::Log.debug('Skipping set_commit_template: not in repo') return end realpath = if @repo_config['commit_template'].start_with?('/') @repo_config['commit_template'] else "#{repo_root}/#{@repo_config['commit_template']}" end unless File.exist?(realpath) die( "Repo config specifies #{@repo_config['commit_template']} as the " + 'commit template, but that file does not exist.', ) end s = git_nofail('config', '--local', 'commit.template') unless s.error? current = s.stdout.strip if current == @repo_config['commit_template'] SugarJar::Log.debug('Commit template already set correctly') return else SugarJar::Log.warn( "Updating repo-specific commit template from #{current} " + "to #{@repo_config['commit_template']}", ) end end SugarJar::Log.debug( 'Setting repo-specific commit template to ' + "#{@repo_config['commit_template']} per sugarjar repo config.", ) git( 'config', '--local', 'commit.template', @repo_config['commit_template'] ) end def get_checks_from_command(type) return nil unless @repo_config["#{type}_list_cmd"] cmd = @repo_config["#{type}_list_cmd"] short = cmd.split.first unless File.exist?(short) SugarJar::Log.error( "Configured #{type}_list_cmd #{short} does not exist!", ) return false end s = Mixlib::ShellOut.new(cmd).run_command if s.error? SugarJar::Log.error( "#{type}_list_cmd (#{cmd}) failed: #{s.format_for_exception}", ) return false end s.stdout.split("\n") end # determine if we're using the _list_cmd and if so run it to get the # checks, or just use the directly-defined check, and cache it def get_checks(type) return @checks[type] if @checks[type] ret = get_checks_from_command(type) if ret SugarJar::Log.debug("Found #{type}s: #{ret}") @checks[type] = ret # if it's explicitly false, we failed to run the command elsif ret == false @checks[type] = false # otherwise, we move on (basically: it's nil, there was no _list_cmd) else SugarJar::Log.debug("[#{type}]: using listed linters: #{ret}") @checks[type] = @repo_config[type] || [] end @checks[type] end def run_check(type) Dir.chdir repo_root do checks = get_checks(type) # if we failed to determine the checks, the the checks have effectively # failed return false unless checks checks.each do |check| SugarJar::Log.debug("Running #{type} #{check}") short = check.split.first if short.include?('/') short = File.join(repo_root, short) unless short.start_with?('/') unless File.exist?(short) SugarJar::Log.error("Configured #{type} #{short} does not exist!") end elsif !which_nofail(short) SugarJar::Log.error("Configured #{type} #{short} does not exist!") return false end s = Mixlib::ShellOut.new(check).run_command # Linters auto-correct, lets handle that gracefully if type == 'lint' && dirty? SugarJar::Log.info( "[#{type}] #{short}: #{color('Corrected', :yellow)}", ) SugarJar::Log.warn( "The linter modified the repo. Here's the diff:\n", ) puts git('diff').stdout loop do $stdout.print( "\nWould you like to\n\t[q]uit and inspect\n\t[a]mend the " + "changes to the current commit and re-run\n > ", ) ans = $stdin.gets.strip case ans when /^q/ SugarJar::Log.info('Exiting at user request.') exit(1) when /^a/ qamend('-a') # break here, if we get out of this loop we 'redo', assuming # the user chose this option break end end redo end if s.error? SugarJar::Log.info( "[#{type}] #{short} #{color('failed', :red)}, output follows " + "(see debug for more)\n#{s.stdout}", ) SugarJar::Log.debug(s.format_for_exception) return false end SugarJar::Log.info( "[#{type}] #{short}: #{color('OK', :green)}", ) end end end def run_prepush @repo_config['on_push']&.each do |item| SugarJar::Log.debug("Running on_push check type #{item}") unless send(:run_check, item) SugarJar::Log.info("[prepush]: #{item} #{color('failed', :red)}.") return false end end true end def die(msg) SugarJar::Log.fatal(msg) exit(1) end def assert_common_main_branch upstream_branch = main_remote_branch(upstream) unless main_branch == upstream_branch die( "The local main branch is '#{main_branch}', but the main branch " + "of the #{upstream} remote is '#{upstream_branch}'. You probably " + "want to rename your local branch by doing:\n\t" + "git branch -m #{main_branch} #{upstream_branch}\n\t" + "git fetch #{upstream}\n\t" + "git branch -u #{upstream}/#{upstream_branch} #{upstream_branch}\n" + "\tgit remote set-head #{upstream} -a", ) end return if upstream_branch == 'origin' origin_branch = main_remote_branch('origin') return if origin_branch == upstream_branch die( "The main branch of your upstream (#{upstream_branch}) and your " + "fork/origin (#{origin_branch}) are not the same. You should go " + "to https://#{@ghhost || 'github.com'}/#{@ghuser}/#{repo_name}/" + 'branches/ and rename the \'default\' branch to ' + "'#{upstream_branch}'. It will then give you some commands to " + 'run to update this clone.', ) end def assert_in_repo die('sugarjar must be run from inside a git repo') unless in_repo end def determine_main_branch(branches) branches.include?('main') ? 'main' : 'master' end def main_branch @main_branch = determine_main_branch(all_local_branches) end def main_remote_branch(remote) @main_remote_branches[remote] ||= determine_main_branch(all_remote_branches(remote)) end def checkout_main_branch git('checkout', main_branch) end def clean_branch(name) die("Cannot remove #{name} branch") if MAIN_BRANCHES.include?(name) SugarJar::Log.debug('Fetch relevant remote...') fetch_upstream return false unless safe_to_clean(name) SugarJar::Log.debug('branch deemed safe to delete...') checkout_main_branch git('branch', '-D', name) gitup true end def all_remote_branches(remote = 'origin') branches = [] git('branch', '-r', '--format', '%(refname)').stdout.lines.each do |line| next unless line.start_with?("refs/remotes/#{remote}/") branches << branch_from_ref(line.strip, :remote) end branches end def all_local_branches branches = [] git('branch', '--format', '%(refname)').stdout.lines.each do |line| branches << branch_from_ref(line.strip) end branches end def safe_to_clean(branch) # cherry -v will output 1 line per commit on the target branch # prefixed by a - or + - anything with a - can be dropped, anything # else cannot. out = git( 'cherry', '-v', tracked_branch, branch ).stdout.lines.reject do |line| line.start_with?('-') end if out.empty? SugarJar::Log.debug( "cherry-pick shows branch #{branch} obviously safe to delete", ) return true end # if the "easy" check didn't work, it's probably because there # was a squash-merge. To check for that we make our own squash # merge to upstream/main and see if that has any delta # First we need a temp branch to work on tmpbranch = "_sugar_jar.#{Process.pid}" git('checkout', '-b', tmpbranch, tracked_branch) s = git_nofail('merge', '--squash', branch) if s.error? cleanup_tmp_branch(tmpbranch, branch) SugarJar::Log.debug( 'Failed to merge changes into current main. This means we could ' + 'not figure out if this is merged or not. Check manually and use ' + "'git branch -D #{branch}' if it is safe to do so.", ) return false end s = git('diff', '--staged') out = s.stdout SugarJar::Log.debug("Squash-merged diff: #{out}") cleanup_tmp_branch(tmpbranch, branch) if out.empty? SugarJar::Log.debug( 'After squash-merging, this branch appears safe to delete', ) true else SugarJar::Log.debug( 'After squash-merging, this branch is NOT fully merged to main', ) false end end def cleanup_tmp_branch(tmp, backto) git('reset', '--hard', tracked_branch) git('checkout', backto) git('branch', '-D', tmp) end def current_branch branch_from_ref(git('symbolic-ref', 'HEAD').stdout.strip) end def fetch_upstream us = upstream fetch(us) if us end def fetch(remote) git('fetch', remote) end def gitup SugarJar::Log.debug('Fetching upstream') fetch_upstream curr = current_branch base = tracked_branch if !MAIN_BRANCHES.include?(curr) && base == "origin/#{curr}" SugarJar::Log.warn( "This branch is tracking origin/#{curr}, which is probably your " + 'downstream (where you push _to_) as opposed to your upstream ' + '(where you pull _from_). This means that "sj up" is probably ' + 'rebasing on the wrong thing and doing nothing. You probably want ' + 'to do a "git branch -u upstream".', ) end SugarJar::Log.debug('Rebasing') s = git_nofail('rebase', base) { 'so' => s, 'base' => base, } end def rebase_in_progress? # for rebase without -i rebase_file = git('rev-parse', '--git-path', 'rebase-apply').stdout.strip # for rebase -i rebase_merge_file = git('rev-parse', '--git-path', 'rebase-merge'). stdout.strip File.exist?(rebase_file) || File.exist?(rebase_merge_file) end def tracked_branch s = git_nofail( 'rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}' ) if s.error? most_main else s.stdout.strip end end def most_main us = upstream if us "#{us}/#{main_branch}" else main_branch end end def upstream return @remote if @remote s = git('remote') remotes = s.stdout.lines.map(&:strip) SugarJar::Log.debug("remotes is #{remotes}") if remotes.empty? @remote = nil elsif remotes.length == 1 @remote = remotes[0] elsif remotes.include?('upstream') @remote = 'upstream' elsif remotes.include?('origin') @remote = 'origin' else raise 'Could not determine "upstream" remote to use...' end @remote end def branch_from_ref(ref, type = :local) # local branches are refs/head/XXXX # remote branches are refs/remotes//XXXX base = type == :local ? 2 : 3 ref.split('/')[base..].join('/') end def color(string, *colors) if @color pastel.decorate(string, *colors) else string end end def pastel @pastel ||= begin require 'pastel' Pastel.new end end def determine_cli(cli) return cli if %w{gh hub}.include?(cli) die("'github_cli' has unknown setting: #{cli}") unless cli == 'auto' SugarJar::Log.debug('github_cli set to auto') if which_nofail('gh') SugarJar::Log.debug('Found "gh"') return 'gh' end if which_nofail('hub') SugarJar::Log.debug('Did not find "gh" but did find "hub"') return 'hub' end die( 'Neither "gh" nor "hub" found in PATH, please ensure at least one ' + 'of these utilities is in the PATH. If both are available you can ' + 'specify which to use with --github-cli', ) end def hub? @cli == 'hub' end def gh? @cli == 'gh' end def ghcli_nofail(*args) gh? ? gh_nofail(*args) : hub_nofail(*args) end def ghcli(*args) gh? ? gh(*args) : hub(*args) end end end sugarjar-1.1.1/lib/sugarjar/config.rb000066400000000000000000000016231456256362600175270ustar00rootroot00000000000000require 'yaml' require_relative 'log' class SugarJar # This parses SugarJar configs (not to be confused with repoconfigs). # This is stuff like log level, github-user, etc. class Config DEFAULTS = { 'github_cli' => 'auto', 'github_user' => ENV.fetch('USER'), 'fallthru' => true, }.freeze def self._find_ordered_files [ '/etc/sugarjar/config.yaml', "#{Dir.home}/.config/sugarjar/config.yaml", ].select { |f| File.exist?(f) } end def self.config SugarJar::Log.debug("Defaults: #{DEFAULTS}") c = DEFAULTS.dup _find_ordered_files.each do |f| SugarJar::Log.debug("Loading config #{f}") data = YAML.safe_load_file(f) # an empty file is a `nil` which you can't merge c.merge!(YAML.safe_load_file(f)) if data SugarJar::Log.debug("Modified config: #{c}") end c end end end sugarjar-1.1.1/lib/sugarjar/log.rb000066400000000000000000000007151456256362600170440ustar00rootroot00000000000000require 'mixlib/log' module Mixlib module Log # A simple formatter so that 'info' is just like 'puts' # but everything else gets a severity class Formatter def call(severity, _time, _progname, msg) if severity == 'INFO' "#{msg2str(msg)}\n" else "#{severity}: #{msg2str(msg)}\n" end end end end end class SugarJar # Our singleton logger class Log extend Mixlib::Log end end sugarjar-1.1.1/lib/sugarjar/repoconfig.rb000066400000000000000000000027371456256362600204240ustar00rootroot00000000000000require_relative 'util' require_relative 'log' require 'yaml' require 'deep_merge' class SugarJar # This parses SugarJar repoconfigs (not to be confused with configs). # This is lint/unit/on_push configs. class RepoConfig extend SugarJar::Util CONFIG_NAME = '.sugarjar.yaml'.freeze def self.repo_config_path(config) ::File.join(repo_root, config) end def self.hash_from_file(config_file) SugarJar::Log.debug("Loading repo config: #{config_file}") YAML.safe_load_file(config_file) end # wrapper for File.exist to make unittests easier def self.config_file?(config_file) File.exist?(config_file) end def self.config(config = CONFIG_NAME) data = {} unless in_repo SugarJar::Log.debug('Not in repo, skipping repoconfig load') return data end config_file = repo_config_path(config) data = hash_from_file(config_file) if config_file?(config_file) if data['overwrite_from'] && config_file?(data['overwrite_from']) SugarJar::Log.debug( "Attempting overwrite_from #{data['overwrite_from']}", ) data = config(data['overwrite_from']) data.delete('overwrite_from') elsif data['include_from'] && config_file?(data['include_from']) SugarJar::Log.debug("Attempting include_from #{data['include_from']}") data.deep_merge!(config(data['include_from'])) data.delete('include_from') end data end end end sugarjar-1.1.1/lib/sugarjar/util.rb000066400000000000000000000110751456256362600172410ustar00rootroot00000000000000require_relative 'log' require 'mixlib/shellout' class SugarJar # Some common methods needed by other classes module Util # Finds the first entry in the path for a binary and checks # to make sure it's not us (i.e. we may be linked to as 'git' # or 'hub', but when we are calling that, we don't want ourselves. def which_nofail(cmd) ENV['PATH'].split(File::PATH_SEPARATOR).each do |dir| p = File.join(dir, cmd) # if it exists, and it is executable and is not us... if File.exist?(p) && File.executable?(p) && File.basename(File.realpath(p)) != 'sj' return p end end false end def which(cmd) path = which_nofail(cmd) return path if path SugarJar::Log.fatal("Could not find #{cmd} in your path") exit(1) end def git_nofail(*args) if %w{diff log grep branch}.include?(args[0]) && args.none? { |x| x.include?('color') } args << (@color ? '--color' : '--no-color') end SugarJar::Log.trace("Running: git #{args.join(' ')}") Mixlib::ShellOut.new([which('git')] + args).run_command end def git(*args) s = git_nofail(*args) s.error! s end def hub_nofail(*args) # this allows us to use 'hub' stuff that's top-level, but is under # repo for this. args.delete_at(0) if args[0] == 'repo' SugarJar::Log.trace("Running: hub #{args.join(' ')}") s = Mixlib::ShellOut.new([which('hub')] + args).run_command if s.error? # depending on hub version and possibly other things, STDERR # is either "Requires authentication" or "Must authenticate" case s.stderr when /^(Must|Requires) authenticat/ SugarJar::Log.info( 'Hub was run but no github token exists. Will run "hub api user" ' + "to force\nhub to authenticate...", ) unless system(which('hub'), 'api', 'user') SugarJar::Log.fatal( 'That failed, I will bail out. Hub needs to get a github ' + 'token. Try running "hub api user" (will list info about ' + 'your account) and try this again when that works.', ) exit(1) end SugarJar::Log.info('Re-running original hub command...') s = Mixlib::ShellOut.new([which('hub')] + args).run_command when /^fatal: could not read Username/, /Anonymous access denied/ # On http(s) URLs, git may prompt for username/passwd SugarJar::Log.info( 'Hub was run but git prompted for authentication. This probably ' + "means you have\nused an http repo URL instead of an ssh one. It " + "is recommended you reclone\nusing 'sj sclone' to setup your " + "remotes properly. However, in the meantime,\nwe'll go ahead " + "and re-run the command in a shell so you can type in the\n" + 'credentials.', ) unless system(which('hub'), *args) SugarJar::Log.fatal( 'That failed, I will bail out. You can either manually change ' + 'your remotes, or simply create a fresh clone with ' + '"sj smartclone".', ) exit(1) end SugarJar::Log.info('Re-running original hub command...') s = Mixlib::ShellOut.new([which('hub')] + args).run_command end end s end def hub(*args) s = hub_nofail(*args) s.error! s end def gh_nofail(*args) SugarJar::Log.trace("Running: gh #{args.join(' ')}") s = Mixlib::ShellOut.new([which('gh')] + args).run_command if s.error? && s.stderr.include?('gh auth') SugarJar::Log.info( 'gh was run but no github token exists. Will run "gh auth login" ' + "to force\ngh to authenticate...", ) unless system(which('gh'), 'auth', 'login', '-p', 'ssh') SugarJar::Log.fatal( 'That failed, I will bail out. Hub needs to get a github ' + 'token. Try running "gh auth login" (will list info about ' + 'your account) and try this again when that works.', ) exit(1) end end s end def gh(*args) s = gh_nofail(*args) s.error! s end def in_repo s = git_nofail('rev-parse', '--is-inside-work-tree') !s.error? && s.stdout.strip == 'true' end def repo_root git('rev-parse', '--show-toplevel').stdout.strip end def repo_name repo_root.split('/').last end end end sugarjar-1.1.1/lib/sugarjar/version.rb000066400000000000000000000000561456256362600177460ustar00rootroot00000000000000class SugarJar VERSION = '1.1.1'.freeze end sugarjar-1.1.1/packaging/000077500000000000000000000000001456256362600152735ustar00rootroot00000000000000sugarjar-1.1.1/packaging/README.md000066400000000000000000000043501456256362600165540ustar00rootroot00000000000000# Fedora Packaging Notes This is mostly notes to myself. ## Refs Some links and refs useful to keep handy * [Sugar Jar dist-git](https://src.fedoraproject.org/rpms/rubygem-sugarjar) * [Package Maintenance Guide](https://docs.fedoraproject.org/en-US/package-maintainers/Package_Maintenance_Guide/) * [Machines you can use](https://fedoraproject.org/wiki/Test_Machine_Resources_For_Package_Maintainers) ## Prep Start the vagrant machine, ssh to it (`vagrant up; vagrant ssh`) If not already checked out, check out the dist-git: ```shell fedpkg co rubygem-sugarjar ``` Make sure you start on the 'rawhide' branch. If already checked out, do `fedpkg pull` to get the latest. ## Do work Make whatever changes you want on rawhide. If you're doing a version bump you'll need to grab both new sources and replace the old ones. First follow the directions in the spec file to build the tarball for the test files. Then wget the gem from the URL in the spec file. Then: ```shell fedpkg new-sources rubygem-sugarjar--specs.tar sugarjar-.gem ``` ## Testing You can do a local build (`fedpkg local`) or a mock build (`fedpkg mockbuild`). You can, alternatively, submit a koji build: ```shell # build a SRPM fedpkg srpm # make sure your krb-auth'd krb # Submit the koji build koji build --scratch rawhide ``` ## Committing and pushing First, commit your change: ```shell fedpkg commit ``` You can push directly to master if you want (`fedpkg push`), or alternatively, make a PR by adding your remote: ```shell git remote add fork ssh://jaymzh@pkgs.fedoraproject.org/forks/jaymzh/rpms/rubygem-sugarjar.git ``` And push to that instead (`git push fork`), and click the link to make a PR. Once it's pushed/merged, you can create a build: ```shell fedpkg build ``` For Rawhide, if the build succeeds, you're done. To build for other distros, switch branches with: ```shell fedpkg switch-branch ``` And you can just merge in rawhide (`git merge rawhide`), then build. For non-rawhide branches, after the `build`, submit the update: ```shell fedpkg update ``` That will push it to testing. Autokarma should push it to stable after about a week (though you can manually push it with `bodhi updates request stable`). sugarjar-1.1.1/packaging/Vagrantfile000066400000000000000000000007341456256362600174640ustar00rootroot00000000000000Vagrant.configure('2') do |config| config.vm.provider :virtualbox do |v| v.customize ['modifyvm', :id, '--memory', 2048] v.name = 'f36' end config.vm.box = 'fedora/36-cloud-base' config.vm.provision :shell, :path => 'provision.sh' config.vm.synced_folder '..', '/home/vagrant/sugarjar' config.vm.synced_folder '../../pastel', '/home/vagrant/pastel' config.vm.synced_folder '../../tty-color', '/home/vagrant/tty-color' config.ssh.insert_key = false end sugarjar-1.1.1/packaging/provision.sh000077500000000000000000000010361456256362600176620ustar00rootroot00000000000000#!/bin/bash sudo dnf install fedora-packager fedora-review rubygems-devel \ rubygem-rspec rubygem-gem2rpm git -y sudo usermod -a -G mock vagrant newgrp echo 'jaymzh' > ~vagrant/.fedora.upn mkdir ~vagrant/bin cat > ~vagrant/bin/krb < ~vagrant/.gitconfig <> ~vagrant/.bashrc <<'EOF' source /usr/share/git-core/contrib/completion/git-prompt.sh export PS1="[\u@\h\$(__git_ps1) \W]\$ " EOF sugarjar-1.1.1/rubygem-sugarjar.spec000066400000000000000000000045171456256362600175200ustar00rootroot00000000000000# tests won't work until dependent packages are available %bcond_without tests %global app_root %{_datadir}/%{name} %global gem_name sugarjar %global version 0.0.10 %global release 1 %global common_description %{expand: Sugarjar is a utility to help making working with git and GitHub easier. In particular it has a lot of features to make rebase-based and squash-based workflows simpler.} Name: rubygem-%{gem_name} Summary: A git/github helper utility Version: %{version} Release: %{release}%{?dist} License: ASL 2.0 URL: http://www.github.com/jaymzh/sugarjar BuildRequires: rubygems-devel BuildRequires: rubygem-mixlib-shellout %if %{with tests} BuildRequires: rubygem-rspec BuildRequires: rubygem-mixlib-log BuildRequires: hub %endif Requires: hub Requires: git-core BuildArch: noarch Source0: https://rubygems.org/downloads/%{gem_name}-%{version}.gem # git clone https://github.com/jaymzh/sugarjar.git # git checkout v0.0.10 # tar -cf rubygem-sugarjar-0.0.10-specs.tar.gz spec/ Source1: %{name}-%{version}-specs.tar.gz %description %{common_description} %package -n sugarjar Summary: A git/github helper utility Requires: hub, git %description -n sugarjar %{common_description} %prep %setup -q -n %{gem_name}-%{version} -b 1 %build gem build ../%{gem_name}-%{version}.gemspec %gem_install %install mkdir -p %{buildroot}%{gem_dir} cp -a ./%{gem_dir}/* %{buildroot}%{gem_dir}/ mkdir -p %{buildroot}%{_bindir} cp -a ./%{_bindir}/* %{buildroot}%{_bindir} find %{buildroot}%{gem_instdir}/bin -type f | xargs chmod a+x %if %{with tests} %check cd .. ln -s sugarjar-%{version}/lib . find rspec spec %endif %clean rm -rf %{buildroot} %files -n sugarjar %dir %{gem_instdir} %{_bindir}/sj %{gem_instdir}/bin %license %{gem_instdir}/LICENSE %doc %{gem_instdir}/README.md %{gem_libdir} %exclude %{gem_cache} %exclude %{gem_instdir}/{Gemfile,sugarjar.gemspec} # We don't have ri/rdoc in our sources %exclude %{gem_docdir} %{gem_spec} %changelog * Tue Aug 23 2022 Phil Dibowitz - 0.0.10-1 - Update to upstream 0.0.10 * Mon Mar 08 2021 Phil Dibowitz - 0.0.9-3 - Add rspec BuildRequires for tests * Mon Mar 01 2021 Phil Dibowitz - 0.0.9-2 - Use global instead of define - Mark the license as a license - Re-enable tests now that rubygem-mixlib-log exists * Sun Feb 28 2021 Phil Dibowitz - 0.0.9-1 - Initial package sugarjar-1.1.1/scripts/000077500000000000000000000000001456256362600150365ustar00rootroot00000000000000sugarjar-1.1.1/scripts/get_linters000077500000000000000000000000741456256362600173040ustar00rootroot00000000000000echo "scripts/run_rubocop.sh -D -a scripts/run_mdl.sh -g ." sugarjar-1.1.1/scripts/lint000077500000000000000000000002201456256362600157240ustar00rootroot00000000000000#!/bin/bash scripts/run_rubocop.sh -D "$@" || { echo "Rubocop failed"; exit 1; } scripts/run_mdl.sh "$@" || { echo "Rubocop failed"; exit 1; } sugarjar-1.1.1/scripts/run_mdl.sh000077500000000000000000000004431456256362600170360ustar00rootroot00000000000000#!/bin/bash SCRIPTS=$(dirname "$(realpath "$0")") REPODIR="$SCRIPTS/.." BIN=$(type mdl | awk '{print $NF}') if [ -n "$1" ]; then args=( "$@" ) else cd "$REPODIR" || { echo "Failed to cd to repo root"; exit 1; } args=('.') fi # shellcheck disable=SC2086 exec $BIN "${args[@]}" sugarjar-1.1.1/scripts/run_rspec.sh000077500000000000000000000005011456256362600173710ustar00rootroot00000000000000#!/bin/bash SCRIPTS=$(dirname "$(realpath "$0")") REPODIR="$SCRIPTS/.." BIN='bundle exec rspec' CMD="$BIN --format d" if [ -n "$1" ]; then args=( "$@" ) else cd "$REPODIR" || { echo "Failed to cd to repo root"; exit 1; } args=() fi # shellcheck disable=SC2086 echo $CMD "${args[@]}" exec $CMD "${args[@]}" sugarjar-1.1.1/scripts/run_rubocop.sh000077500000000000000000000005141456256362600177320ustar00rootroot00000000000000#!/bin/bash SCRIPTS=$(dirname "$(realpath "$0")") REPODIR="$SCRIPTS/.." BIN='bundle exec rubocop' CMD="$BIN --display-cop-names" if [ -n "$1" ]; then args=( "$@" ) else cd "$REPODIR" || { echo "Failed to cd to repo root"; exit 1; } args=() fi # shellcheck disable=SC2086 echo $CMD "${args[@]}" exec $CMD "${args[@]}" sugarjar-1.1.1/scripts/unit000077500000000000000000000001131456256362600157360ustar00rootroot00000000000000#!/bin/bash scripts/run_rspec.sh "$@" || { echo "rspec failed"; exit 1; } sugarjar-1.1.1/smartlog.png000066400000000000000000000111151456256362600157040ustar00rootroot00000000000000PNG  IHDRliCCPICC profile(}=H@ߦTD ␡:Yq*BZu04$).kŪ "%~Zxpw}wШ046ɄͭWDЏ!YƜ$;]g9zռŀH< & ޴ QVUs1.Hu7E53y(X`YԈcSXYXuHbK BA eT`#NN4'|C_"B29PZ /)B/1wfqy+6Om-vmmM.w'C6eW  ) =k^Z8}2ԫ pp){ݝ}տrpSac pHYs~tIME  AUtEXtCommentCreated with GIMPW7IDATxkqWv4_B_sBŖ9ms?݅sӥ;϶G?,"2JI7/+}9,Tx)p8v,z^+DJ J,MǡƊޮ#/i7јٯ+͟[uy|mf|LVsS)^4*'jϰX#8?~;הO/M}$6qKW'[ X-FP: Ls!x|5 l|aLXSׇ^qhQnP,y87B[+JOIeGMF^ tl6Q#.|j뤑^ι`NklF[a6c.M#d'S&-%&GV6~>~s. 3]s*M?ÔJrDkJc`GPW^Rfu@nd( E'ӿlopڛ9:͖&~ӣa"Te].YN\l〞DߢL>i6:}s6Aǩ@xûPwNEk)f/`ɦw}`'z[kW ͳd%FlG5}-H䃅s"f7ĉZ$)e#a8*sDVs׌VJsآBE?[CN> {b!`G ߴ·jQg}>+0?_? 9[:vγ z ɤ+=q^>}Kk:瞃uȄL.MMzZ.[:[7 MaKeYS-@|~yиw+XH1M:!"ҝ]ỵ>~Q^fE5Y6P]\yM+-XF|8ӻEa[L^n+$qStG>zg3CֵkťZbQ2_jPOæ}ą;t#0Xҡ?Tv'q I/xnKGrFo wn`5M}45d[m>,~|w"Ih 74Z*[.,>_@uuf 5 gjhrp`uqo}N`pEjpk~ܗt҇x?Q@ 0*Ǝ_ 7MK·cQ;NRsGsfeG'O?r D xs,M".G?G{ϞVWQ:(qWCa)| CTYȿQ|Lt9YG͠^qn^$rx"o]4Me wE;$j#dhD|5oM2l<5_d-w3mǻGgrIIW:kvqd*F+lWE=|iF]/h4?keqhМlS6h׊eG5/S*jy&Myц&JmrPxX] z=twa6Q4j6VcW#U}r$g>=-^}NYXV HvҗբTOҥ'6] l#z4;M_'hV:u *2g}MCZ#/iÚ+b,|L|I[riʌb>z}x=J"G%TqqLo"1{9i w{0i:'T׬᛭QX7h;kx^1M;@ 9R6MeۗA)$ϙb&÷)".zFee*^T8ᡱsYu[iL}!IPF.vr%v78Fy87iqlu{\:cPr>Ṙ~rz[ sѺ{< e5iRB~L0 *[C[qS.•̉NM+.zkȀ*%2 CFM6[֭v4Xƿc7@dQ&Ul~OmEO[R19٢l-z#eT 8}j6Q7s2fVDA'Ly.[zؼ`iǍ87zUÂSW\(( ~S ^ mIxϕ.ސ[D_ 9 vY40nCL)M跥3Hu%ؠ$37 5]qI5J est]@+m`g9kF \ 'Sgڠĭxri9LظhMʹNJ_{C_Ui?뽒k(LfLVmsW3&)ä8LnWcy Ikuo\mKvweF@o=z$--SK=g-@-U,*&E^#j2;fN>B4BBLD3<,Rׇ8ZN P _p(&C5n GaF?̧>vi8+ x\\@ވ?G>7+3JԑCT q09U7o*c6rոIENDB`sugarjar-1.1.1/spec/000077500000000000000000000000001456256362600143015ustar00rootroot00000000000000sugarjar-1.1.1/spec/commands_spec.rb000066400000000000000000000140521456256362600174430ustar00rootroot00000000000000# For Ruby packages, Debian autopkgtest runs in an environment where # gem2deb-test-runner removes the lib directory from the source tree, so # the specs have to be able to load the installed copy instead. # # add '../lib' to the front of the path, so that when requiring modules, the # ones in '../lib' are still going to be used if available, but we can fall # back to an installed module # # See https://wiki.debian.org/Teams/Ruby/Packaging/Tests#Case_eight:_autopkgtest_failure $LOAD_PATH.unshift File.expand_path('../lib', __dir__) require 'sugarjar/commands' describe 'SugarJar::Commands' do context '#set_commit_template' do it 'Does nothing if not in repo' do expect(SugarJar::RepoConfig).to receive(:config).and_return( { 'commit_template' => '.commit_template.txt' }, ) sj = SugarJar::Commands.new({ 'no_change' => true }) expect(sj).to receive(:in_repo).and_return(false) expect(SugarJar::Log).to receive(:debug).with(/Skipping/) sj.send(:set_commit_template) end it 'Errors out of template does not exist' do expect(SugarJar::RepoConfig).to receive(:config).and_return( { 'commit_template' => '.commit_template.txt' }, ) sj = SugarJar::Commands.new({ 'no_change' => true }) expect(sj).to receive(:in_repo).and_return(true) expect(sj).to receive(:repo_root).and_return('/nonexistent') expect(File).to receive(:exist?). with('/nonexistent/.commit_template.txt').and_return(false) expect(SugarJar::Log).to receive(:fatal).with(/exist/) expect { sj.send(:set_commit_template) }.to raise_error(SystemExit) end it 'Does not set the template if it is already set' do expect(SugarJar::RepoConfig).to receive(:config).and_return( { 'commit_template' => '.commit_template.txt' }, ) sj = SugarJar::Commands.new({ 'no_change' => true }) expect(sj).to receive(:in_repo).and_return(true) expect(sj).to receive(:repo_root).and_return('/nonexistent') expect(File).to receive(:exist?). with('/nonexistent/.commit_template.txt').and_return(true) so = double('shell_out') expect(so).to receive(:error?).and_return(false) expect(so).to receive(:stdout).and_return(".commit_template.txt\n") expect(sj).to receive(:git_nofail).and_return(so) expect(SugarJar::Log).to receive(:debug).with(/already/) sj.send(:set_commit_template) end it 'warns (and sets) if overwriting template' do expect(SugarJar::RepoConfig).to receive(:config).and_return( { 'commit_template' => '.commit_template.txt' }, ) sj = SugarJar::Commands.new({ 'no_change' => true }) expect(sj).to receive(:in_repo).and_return(true) expect(sj).to receive(:repo_root).and_return('/nonexistent') expect(File).to receive(:exist?). with('/nonexistent/.commit_template.txt').and_return(true) so = double('shell_out') expect(so).to receive(:error?).and_return(false) expect(so).to receive(:stdout).and_return(".not_commit_template.txt\n") expect(sj).to receive(:git_nofail).and_return(so) expect(sj).to receive(:git).with( 'config', '--local', 'commit.template', '.commit_template.txt' ) expect(SugarJar::Log).to receive(:warn).with(/^Updating/) sj.send(:set_commit_template) end it 'sets the template when none is set' do expect(SugarJar::RepoConfig).to receive(:config).and_return( { 'commit_template' => '.commit_template.txt' }, ) sj = SugarJar::Commands.new({ 'no_change' => true }) expect(sj).to receive(:in_repo).and_return(true) expect(sj).to receive(:repo_root).and_return('/nonexistent') expect(File).to receive(:exist?). with('/nonexistent/.commit_template.txt').and_return(true) so = double('shell_out') expect(so).to receive(:error?).and_return(true) expect(sj).to receive(:git_nofail).and_return(so) expect(sj).to receive(:git).with( 'config', '--local', 'commit.template', '.commit_template.txt' ) expect(SugarJar::Log).to receive(:debug).with(/^Setting/) sj.send(:set_commit_template) end end context '#extract_org' do let(:sj) do SugarJar::Commands.new({ 'no_change' => true }) end [ # ssh 'git@github.com:org/repo.git', # http 'http://github.com/org/repo.git', # https 'https://github.com/org/repo.git', # hub 'org/repo', ].each do |url| it "detects the org from #{url}" do expect(sj.send(:extract_org, url)).to eq('org') end end end context '#forked_repo' do let(:sj) do SugarJar::Commands.new({ 'no_change' => true }) end [ # ssh 'git@github.com:org/repo.git', # http 'http://github.com/org/repo.git', # https 'https://github.com/org/repo.git', # hub 'org/repo', ].each do |url| it "generates correct URL from #{url}" do expect(sj.send(:forked_repo, url, 'test')). to eq('git@github.com:test/repo.git') end end end context '#canonicalize_repo' do let(:sj) do SugarJar::Commands.new({ 'no_change' => true }) end [ # ssh 'git@github.com:org/repo.git', # http 'http://github.com/org/repo.git', # https 'https://github.com/org/repo.git', ].each do |url| it "keeps fully-qualified URL #{url} the same" do expect(sj.send(:canonicalize_repo, url)).to eq(url) end end # hub url = 'org/repo' it "canonicalizes short name #{url}" do expect(sj.send(:canonicalize_repo, url)). to eq('git@github.com:org/repo.git') end end context '#fprefix' do it 'Adds prefixes when needed' do sj = SugarJar::Commands.new( { 'no_change' => true, 'feature_prefix' => 'someuser/' }, ) expect(sj.send(:fprefix, 'test')).to eq('someuser/test') end it 'Does not add prefixes when not needed' do sj = SugarJar::Commands.new({ 'no_change' => true }) expect(sj.send(:fprefix, 'test')).to eq('test') end end end sugarjar-1.1.1/spec/repoconfig_spec.rb000066400000000000000000000124141456256362600177750ustar00rootroot00000000000000$LOAD_PATH.unshift File.expand_path('../lib', __dir__) require 'sugarjar/repoconfig' describe 'SugarJar::RepoConfig' do context '#config' do it 'properly reads config' do expected = { 'lint' => [ 'somecommand', 'another command', ], 'unit' => [ 'test', ], 'on_push' => [ 'lint', ], } allow(SugarJar::RepoConfig).to receive(:config_file?). and_return(true) allow(SugarJar::RepoConfig).to receive(:hash_from_file). and_return(expected) data = SugarJar::RepoConfig.config('whatever') # we gave it expected, this test basically makes sure we don't # break the data along the way expect(data).to eq(expected) end it 'merges include_from into config' do base = { 'include_from' => 'additional', 'top1' => ['entryA'], 'top2' => { 'top2key1' => 'a', 'top2key2' => 'b', }, } additional = { # array merge 'top1' => ['entryB'], 'top2' => { # key overwrite 'top2key1' => 'new', # additional key 'top2key3' => 'c', }, } expected = { 'top1' => %w{entryA entryB}, 'top2' => { 'top2key1' => 'new', 'top2key2' => 'b', 'top2key3' => 'c', }, } allow(SugarJar::RepoConfig).to receive(:repo_config_path). with('base').and_return('base') allow(SugarJar::RepoConfig).to receive(:repo_config_path). with('additional').and_return('additional') allow(SugarJar::RepoConfig).to receive(:config_file?). and_return(true) allow(SugarJar::RepoConfig).to receive(:hash_from_file). with('base').and_return(base) allow(SugarJar::RepoConfig).to receive(:hash_from_file). with('additional').and_return(additional) data = SugarJar::RepoConfig.config('base') expect(data).to eq(expected) end it 'overwrites config with overwrite_from' do base = { 'overwrite_from' => 'additional', 'top1' => ['entryA'], 'top2' => { 'top2key1' => 'a', 'top2key2' => 'b', }, } additional = { 'new' => ['thing'], } %w{base additional}.each do |word| allow(SugarJar::RepoConfig).to receive(:repo_config_path). with(word).and_return(word) end allow(SugarJar::RepoConfig).to receive(:config_file?). and_return(true) allow(SugarJar::RepoConfig).to receive(:hash_from_file). with('base').and_return(base) allow(SugarJar::RepoConfig).to receive(:hash_from_file). with('additional').and_return(additional) data = SugarJar::RepoConfig.config('base') # it doesn't matter what's in 'base', we should get 'additional' back expect(data).to eq(additional) end it 'handles recursive includes' do base = { 'include_from' => 'additional', 'top1' => ['entryA'], 'top2' => { 'top2key1' => 'a', 'top2key2' => 'b', }, } additional = { # array merge 'include_from' => 'more', 'top1' => ['entryB'], 'top2' => { # key overwrite 'top2key1' => 'new', # additional key 'top2key3' => 'c', }, } more = { 'other stuff' => { 'things' => 'stuff', }, } expected = { 'top1' => %w{entryA entryB}, 'top2' => { 'top2key1' => 'new', 'top2key2' => 'b', 'top2key3' => 'c', }, 'other stuff' => { 'things' => 'stuff', }, } %w{base additional more}.each do |word| allow(SugarJar::RepoConfig).to receive(:repo_config_path). with(word).and_return(word) end allow(SugarJar::RepoConfig).to receive(:config_file?). and_return(true) allow(SugarJar::RepoConfig).to receive(:hash_from_file). with('base').and_return(base) allow(SugarJar::RepoConfig).to receive(:hash_from_file). with('additional').and_return(additional) allow(SugarJar::RepoConfig).to receive(:hash_from_file). with('more').and_return(more) data = SugarJar::RepoConfig.config('base') expect(data).to eq(expected) end it "doesn't overwrite from non-existent files" do base = { 'include_from' => 'additional', 'top1' => ['entryA'], 'top2' => { 'top2key1' => 'a', 'top2key2' => 'b', }, } additional = { 'something' => 'else', } %w{base additional}.each do |word| allow(SugarJar::RepoConfig).to receive(:repo_config_path). with(word).and_return(word) end allow(SugarJar::RepoConfig).to receive(:config_file?). with('base').and_return(true) allow(SugarJar::RepoConfig).to receive(:config_file?). with('additional').and_return(true) allow(SugarJar::RepoConfig).to receive(:hash_from_file). with('base').and_return(base) allow(SugarJar::RepoConfig).to receive(:hash_from_file). with('additional').and_return(additional) data = SugarJar::RepoConfig.config('base') expect(data).to eq(data) end end end sugarjar-1.1.1/sugarjar.gemspec000066400000000000000000000022431456256362600165330ustar00rootroot00000000000000require_relative 'lib/sugarjar/version' Gem::Specification.new do |spec| spec.name = 'sugarjar' spec.version = SugarJar::VERSION spec.summary = 'A git/github helper script' spec.authors = ['Phil Dibowitz'] spec.email = ['phil@ipom.com'] spec.license = 'Apache-2.0' spec.homepage = 'https://github.com/jaymzh/sugarjar' # We'll support 3.0 until 2024-03-31 when it goes EOL # https://www.ruby-lang.org/en/downloads/branches/ spec.required_ruby_version = '>= 3.0' docs = %w{README.md LICENSE Gemfile sugarjar.gemspec} spec.extra_rdoc_files = docs spec.executables << 'sj' spec.files = Dir.glob('lib/sugarjar/*.rb') + Dir.glob('bin/*') + Dir.glob('extras/*') spec.add_dependency 'deep_merge' spec.add_dependency 'mixlib-log' spec.add_dependency 'mixlib-shellout' spec.add_dependency 'pastel' spec.metadata = { 'rubygems_mfa_required' => 'true', 'bug_tracker_uri' => 'https://github.com/jaymzh/sugarjar/issues', 'changelog_uri' => 'https://github.com/jaymzh/sugarjar/blob/main/CHANGELOG.md', 'homepage_uri' => 'https://github.com/jaymzh/sugajar', 'source_code_uri' => 'https://github.com/jaymzh/sugarjar', } end