sugarjar 0.0.10 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +85 -30
- data/bin/sj +48 -10
- data/lib/sugarjar/commands.rb +293 -60
- data/lib/sugarjar/config.rb +3 -2
- data/lib/sugarjar/util.rb +47 -3
- data/lib/sugarjar/version.rb +1 -1
- metadata +3 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 95e4979017c0ed58f99c446d9215a6b64aec7d5ebc22694a95903710d5af9d7e
         | 
| 4 | 
            +
              data.tar.gz: b4b7a648bc7e8002e46dfbaa73cc52749cd38258b5782a2b33a6d66db98facfc
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 0e34735382f6646bf5fe1577eb6637ad7aaceaa9ccf2d2dad024d4a95f3e2a0e8ec080fe086fd5dae7354b3f15f83d26e6ae8b067a1fae9fb5b3da777f9e1f86
         | 
| 7 | 
            +
              data.tar.gz: 155d1aa1c7148f2d7181678f64ef64b17fb5efc90386ce3bd27c2bcc7b256113680b4e69a16558790ab3c77080325fc36a56c9f3cc76cf4962f8f575cd00d5d7
         | 
    
        data/README.md
    CHANGED
    
    | @@ -3,10 +3,17 @@ | |
| 3 3 | 
             
            [](https://github.com/jaymzh/sugarjar/actions?query=workflow%3ALint)
         | 
| 4 4 | 
             
            [](https://github.com/jaymzh/sugarjar/actions?query=workflow%3AUnittests)
         | 
| 5 5 | 
             
            [](https://github.com/jaymzh/sugarjar/actions?query=workflow%3A%22DCO+Check%22)
         | 
| 6 | 
            -
            [](https://badge.fury.io/rb/sugarjar)
         | 
| 7 6 |  | 
| 8 | 
            -
             | 
| 9 | 
            -
            [ | 
| 7 | 
            +
            > [!IMPORTANT]
         | 
| 8 | 
            +
            > You probably want to use [Sapling](https://sapling-scm.com/)
         | 
| 9 | 
            +
            > instead of this! This was written as a stop-gap to approximate some features
         | 
| 10 | 
            +
            > of the Facebook/Meta internal development tools before they were released.
         | 
| 11 | 
            +
            > Now that those tools have been open-sourced and work with GitHub, this is
         | 
| 12 | 
            +
            > likely to be deprecated at some point. Sapling is far more feature rich,
         | 
| 13 | 
            +
            > faster, and have more development resources behind them.
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            Welcome to SugarJar - a git/github helper. It needs one of the GitHub CLI's:
         | 
| 16 | 
            +
            either [gh](https://cli.github.com/) or the older [hub](https://hub.github.com/).
         | 
| 10 17 |  | 
| 11 18 | 
             
            SugarJar is inspired by [arcanist](https://github.com/phacility/arcanist), and
         | 
| 12 19 | 
             
            its replacement at Facebook, JellyFish. Many of the features they provide for
         | 
| @@ -19,6 +26,30 @@ If you miss Mondrian or Phabricator - this is the tool for you! | |
| 19 26 |  | 
| 20 27 | 
             
            If you don't, there's a ton of useful stuff for everyone!
         | 
| 21 28 |  | 
| 29 | 
            +
            ## Installation
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            Sugarjar is packaged in a variety of Linux distributions - see if it's on the
         | 
| 32 | 
            +
            list here, and if so, use your package manager (or `gem`) to install it:
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            [](https://repology.org/project/sugarjar/versions)
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            For Ubuntu users, Sugarjar will likely be available starting in the upcoming
         | 
| 37 | 
            +
            23.04 release. Until then, for supported LTS releases starting 20.04 (focal)
         | 
| 38 | 
            +
            and the latest non-LTS release, you can use [this PPA](
         | 
| 39 | 
            +
            https://launchpad.net/~michel-slm/+archive/ubuntu/sugarjar) maintained by the
         | 
| 40 | 
            +
            Debian package maintainer.
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            Another option is to use our [omnibus
         | 
| 43 | 
            +
            packages](https://github.com/jaymzh/sugarjar/releases). We keep Omnibus
         | 
| 44 | 
            +
            packages for most distros that do not package Sugarjar. Omnibus packages are
         | 
| 45 | 
            +
            distro packages (deb, rpm, etc.) that have all dependencies bundled up together
         | 
| 46 | 
            +
            (including Ruby), and install in `/opt/sugarjar` allowing you to manage the
         | 
| 47 | 
            +
            installation with your package manager, but with as a hermetically sealed app
         | 
| 48 | 
            +
            that doesn't depend on the rest of your distro.
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            Finally, if none of those work for you, you can clone this repo and run it
         | 
| 51 | 
            +
            directly from there.
         | 
| 52 | 
            +
             | 
| 22 53 | 
             
            ## Auto cleanup squash-merged branches
         | 
| 23 54 |  | 
| 24 55 | 
             
            It is common for a PR to go back and forth with a variety of nits, lint fixes,
         | 
| @@ -73,7 +104,7 @@ This will: | |
| 73 104 | 
             
            * Clone your fork
         | 
| 74 105 | 
             
            * Add the original as an 'upstream' remote
         | 
| 75 106 |  | 
| 76 | 
            -
            Note that it takes  | 
| 107 | 
            +
            Note that it takes short names for repos. No need to specify a full URL,
         | 
| 77 108 | 
             
            just a $org/$repo.
         | 
| 78 109 |  | 
| 79 110 | 
             
            Like `git clone`, `sj sclone` will accept an additional argument as the
         | 
| @@ -198,6 +229,15 @@ smartlog` or `sj sl` for short. | |
| 198 229 |  | 
| 199 230 | 
             
            
         | 
| 200 231 |  | 
| 232 | 
            +
            ## Pulling in suggestions from the web
         | 
| 233 | 
            +
             | 
| 234 | 
            +
            When someone 'suggests' a change in the GitHub WebUI, once you choose to commit
         | 
| 235 | 
            +
            them, your origin and local branches are no longer in-sync. The
         | 
| 236 | 
            +
            `pullsuggestions` command will attempt to merge in any remote commits to your
         | 
| 237 | 
            +
            local branch. This command will show a diff and ask for confirmation before
         | 
| 238 | 
            +
            attempting the merge and  - if allowed to continue - will use a fast-forward
         | 
| 239 | 
            +
            merge.
         | 
| 240 | 
            +
             | 
| 201 241 | 
             
            ## And more!
         | 
| 202 242 |  | 
| 203 243 | 
             
            See `sj help` for more commands!
         | 
| @@ -205,8 +245,11 @@ See `sj help` for more commands! | |
| 205 245 | 
             
            ## Using SugarJar as a git wrapper
         | 
| 206 246 |  | 
| 207 247 | 
             
            SugarJar, by default, will pass any command it doesn't know straight to `hub`
         | 
| 208 | 
            -
            (which passes commands **it** doesn't know to `git`).  | 
| 209 | 
            -
            to ` | 
| 248 | 
            +
            (which passes commands **it** doesn't know to `git`). If you have configured
         | 
| 249 | 
            +
            SugarJar to use `gh` instead of `hub`, then it will pass commands straight to
         | 
| 250 | 
            +
            `git` since `gh` doesn't act as a `git` wrapper.
         | 
| 251 | 
            +
             | 
| 252 | 
            +
            As such you can alias it to `git` and just have a super-git.
         | 
| 210 253 |  | 
| 211 254 | 
             
            ```shell
         | 
| 212 255 | 
             
            $ alias git=sj
         | 
| @@ -248,8 +291,8 @@ log_level: debug | |
| 248 291 | 
             
            github_user: jaymzh
         | 
| 249 292 | 
             
            ```
         | 
| 250 293 |  | 
| 251 | 
            -
            In addition, the environment variable ` | 
| 252 | 
            -
             | 
| 294 | 
            +
            In addition, the environment variable `SUGARJAR_LOGLEVEL` can be defined to set
         | 
| 295 | 
            +
            a log level. This is primarily used as a way to turn debug on earlier in order to
         | 
| 253 296 | 
             
            troubleshoot configuration parsing.
         | 
| 254 297 |  | 
| 255 298 | 
             
            ## Repository Configuration
         | 
| @@ -258,9 +301,15 @@ Sugarjar looks for a `.sugarjar.yaml` in the root of the repository to tell it | |
| 258 301 | 
             
            how to handle repo-specific things. Currently there options are:
         | 
| 259 302 |  | 
| 260 303 | 
             
            * `lint` - A list of scripts to run on `sj lint`. These should be linters like
         | 
| 261 | 
            -
              rubocop or pyflake.
         | 
| 304 | 
            +
              rubocop or pyflake. Linters will be run from the root of the repo.
         | 
| 305 | 
            +
            * `lint_list_cmd` - A command to run which will print out linters to run, one
         | 
| 306 | 
            +
              per line. Takes precedence over `lint`. The command (and the resulting
         | 
| 307 | 
            +
              linters) will be run from the root of the repo.
         | 
| 262 308 | 
             
            * `unit` - A list of scripts to run on `sj unit`. These should be unittest
         | 
| 263 | 
            -
              runners like rspec or pyunit.
         | 
| 309 | 
            +
              runners like rspec or pyunit. Test will be run from the root of the repo.
         | 
| 310 | 
            +
            * `unit_list_cmd` - A command to run which will print out the unit tests to
         | 
| 311 | 
            +
              run, one more line. Takes precedence over `unit`. The command (and the
         | 
| 312 | 
            +
              resulting unit tests) will be run from the root of the repo.
         | 
| 264 313 | 
             
            * `on_push` - A list of types (`lint`, `unit`) of checks to run before pushing.
         | 
| 265 314 | 
             
              It is highly recommended this is only `lint`. The goal here is to allow for
         | 
| 266 315 | 
             
              the user to get quick stylistic feedback before pushing their branch to avoid
         | 
| @@ -312,34 +361,40 @@ sj clone jaymzh/sugarjar --github-host githuh.com | |
| 312 361 | 
             
            We will add the `hub.host` to the `sugarjar` clone so that future `hub` or `sj`
         | 
| 313 362 | 
             
            commands work without needing to specify..
         | 
| 314 363 |  | 
| 315 | 
            -
            ##  | 
| 316 | 
            -
             | 
| 317 | 
            -
            There are many ways to install SugarJar. The easiest is to use one of the
         | 
| 318 | 
            -
            packages we provide in the
         | 
| 319 | 
            -
            [releases](https://github.com/jaymzh/sugarjar/releases) section. Currently we
         | 
| 320 | 
            -
            provide packages for Fedora, CentOS, Debian, and Ubuntu, but if you want
         | 
| 321 | 
            -
            others, file an Issue. Since these packages are
         | 
| 322 | 
            -
            [omnibus](https://github.com/chef/omnibus) packages which means they are bundled
         | 
| 323 | 
            -
            with all of their dependencies. This means these packages will likely work as-is
         | 
| 324 | 
            -
            on later releases of these distros or any similar distros.
         | 
| 364 | 
            +
            ## Choosing a GitHub CLI
         | 
| 325 365 |  | 
| 326 | 
            -
             | 
| 327 | 
            -
             | 
| 328 | 
            -
             | 
| 329 | 
            -
             | 
| 366 | 
            +
            SugarJar will use `gh` if it is available or otherwise fall back to `hub`. You
         | 
| 367 | 
            +
            can override this by specifying `--github-cli` on the command line or setting
         | 
| 368 | 
            +
            `github_cli` to either `gh` or `hub` (it defaults to `auto`) in your
         | 
| 369 | 
            +
            configuration.
         | 
| 330 370 |  | 
| 331 371 | 
             
            ## FAQ
         | 
| 332 372 |  | 
| 333 | 
            -
            Why the name SugarJar | 
| 373 | 
            +
            **Why the name SugarJar?**
         | 
| 334 374 |  | 
| 335 | 
            -
            It's mostly a  | 
| 375 | 
            +
            It's mostly a backronym. Like jellyfish, I wanted two letters that were on home
         | 
| 336 376 | 
             
            row on different sides of the keyboard to make it easy to type. I looked at the
         | 
| 337 377 | 
             
            possible options that where there and not taken and tried to find one I could
         | 
| 338 378 | 
             
            make an appropriate name out of. Since this utility adds lots of sugar to git
         | 
| 339 379 | 
             
            and github, it seemed appropriate.
         | 
| 340 380 |  | 
| 341 | 
            -
            Why did you use `hub` instead of the newer `gh` CLI | 
| 381 | 
            +
            **Why did you originally use `hub` instead of the newer `gh` CLI?**
         | 
| 382 | 
            +
             | 
| 383 | 
            +
            When I originally wrote SugarJar, `gh` was in early development, and `hub` had
         | 
| 384 | 
            +
            many more features. In addition, I wrote SugarJar to be a wrapper for git/hub,
         | 
| 385 | 
            +
            and `hub` allows this but `gh` does not.
         | 
| 386 | 
            +
             | 
| 387 | 
            +
            When `gh` matured, we added experimental `gh` support in 0.0.11, and switched the
         | 
| 388 | 
            +
            default to prefer `gh` in 1.0.0.
         | 
| 389 | 
            +
             | 
| 390 | 
            +
            **I'd like to package SugarJar for my favorite distro/OS, is that OK?**
         | 
| 391 | 
            +
             | 
| 392 | 
            +
            Of course! But I'd appreciate you emailing me to give me a heads up. Doing so
         | 
| 393 | 
            +
            will allow me to make sure it shows up in the Repology badge above as well as
         | 
| 394 | 
            +
            stop building Omnibus packages for whatever distro.
         | 
| 395 | 
            +
             | 
| 396 | 
            +
            **What platforms does it work on?**
         | 
| 342 397 |  | 
| 343 | 
            -
             | 
| 344 | 
            -
             | 
| 345 | 
            -
             | 
| 398 | 
            +
            Since it's Ruby, it should work across all platforms, however, it's developed
         | 
| 399 | 
            +
            and primarily tested on Linux as well as regularly used on Mac. I've not tested
         | 
| 400 | 
            +
            it on Windows, but I'll happily accept patches for Windows compatibility.
         | 
    
        data/bin/sj
    CHANGED
    
    | @@ -29,12 +29,22 @@ parser = OptionParser.new do |opts| | |
| 29 29 | 
             
              opts.separator ''
         | 
| 30 30 | 
             
              opts.separator 'OPTIONS:'
         | 
| 31 31 |  | 
| 32 | 
            -
              opts.on('--[no-]fallthru', 'Fall-thru to git') do |fallthru|
         | 
| 32 | 
            +
              opts.on('--[no-]fallthru', 'Fall-thru to git. [default: true]') do |fallthru|
         | 
| 33 33 | 
             
                options['fallthru'] = fallthru
         | 
| 34 34 | 
             
              end
         | 
| 35 35 |  | 
| 36 | 
            -
              opts.on('-- | 
| 37 | 
            -
                options[' | 
| 36 | 
            +
              opts.on('--feature-prefix', 'Prefix to use for feature branches') do |prefix|
         | 
| 37 | 
            +
                options['feature_prefix'] = prefix
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              opts.on(
         | 
| 41 | 
            +
                '--github-cli CLI',
         | 
| 42 | 
            +
                %w{gh cli},
         | 
| 43 | 
            +
                'Github CLI to use ("gh" or "hub" or "auto"). Auto (the default) will ' +
         | 
| 44 | 
            +
                'prefer "gh" if it is available but will fall back to "hub." ' +
         | 
| 45 | 
            +
                '[default: "auto"]',
         | 
| 46 | 
            +
              ) do |cli|
         | 
| 47 | 
            +
                options['github_cli'] = cli
         | 
| 38 48 | 
             
              end
         | 
| 39 49 |  | 
| 40 50 | 
             
              opts.on(
         | 
| @@ -48,6 +58,10 @@ parser = OptionParser.new do |opts| | |
| 48 58 | 
             
                options['github_host'] = host
         | 
| 49 59 | 
             
              end
         | 
| 50 60 |  | 
| 61 | 
            +
              opts.on('--github-user USER', 'Github username') do |user|
         | 
| 62 | 
            +
                options['github_user'] = user
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
             | 
| 51 65 | 
             
              opts.on('-h', '--help', 'Print this help message') do
         | 
| 52 66 | 
             
                puts opts
         | 
| 53 67 | 
             
                exit
         | 
| @@ -55,22 +69,24 @@ parser = OptionParser.new do |opts| | |
| 55 69 |  | 
| 56 70 | 
             
              opts.on(
         | 
| 57 71 | 
             
                '--ignore-dirty',
         | 
| 58 | 
            -
                'Tell command that check for a dirty repo to carry on anyway.' | 
| 72 | 
            +
                'Tell command that check for a dirty repo to carry on anyway. ' +
         | 
| 73 | 
            +
                '[default: false]',
         | 
| 59 74 | 
             
              ) do
         | 
| 60 75 | 
             
                options['ignore_dirty'] = true
         | 
| 61 76 | 
             
              end
         | 
| 62 77 |  | 
| 63 78 | 
             
              opts.on(
         | 
| 64 79 | 
             
                '--ignore-prerun-failure',
         | 
| 65 | 
            -
                'Ignore preprun failure on *push commands.',
         | 
| 80 | 
            +
                'Ignore preprun failure on *push commands. [default: false]',
         | 
| 66 81 | 
             
              ) do
         | 
| 67 82 | 
             
                options['ignore_prerun_failure'] = true
         | 
| 68 83 | 
             
              end
         | 
| 69 84 |  | 
| 70 85 | 
             
              opts.on(
         | 
| 71 86 | 
             
                '--log-level LEVEL',
         | 
| 72 | 
            -
                'Set logging level (fatal, error, warning, info, debug, trace).  | 
| 73 | 
            -
                ' | 
| 87 | 
            +
                'Set logging level (fatal, error, warning, info, debug, trace). This can ' +
         | 
| 88 | 
            +
                'also be set via the SUGARJAR_LOGLEVEL environment variable. [default: ' +
         | 
| 89 | 
            +
                'info]',
         | 
| 74 90 | 
             
              ) do |level|
         | 
| 75 91 | 
             
                options['log_level'] = level
         | 
| 76 92 | 
             
              end
         | 
| @@ -118,6 +134,12 @@ COMMANDS: | |
| 118 134 | 
             
                          of preference it will be upstream/master, origin/master, master,
         | 
| 119 135 | 
             
                          depending upon what remotes are available.
         | 
| 120 136 |  | 
| 137 | 
            +
                          Note that you can specify "--feature-prefix" (or add
         | 
| 138 | 
            +
                          "feature_prefix" to your config) to have all features created
         | 
| 139 | 
            +
                          with a prefix. This is useful for branch-based workflows where
         | 
| 140 | 
            +
                          developers are expected to create branches names that, for
         | 
| 141 | 
            +
                          example, start with their username.
         | 
| 142 | 
            +
             | 
| 121 143 | 
             
              forcepush, fpush
         | 
| 122 144 | 
             
                          The same as "smartpush", but uses "--force-with-lease". This is
         | 
| 123 145 | 
             
                          a "safer" way of doing force-pushes and is the recommended way
         | 
| @@ -128,6 +150,11 @@ COMMANDS: | |
| 128 150 | 
             
              lint
         | 
| 129 151 | 
             
                          Run any linters configured in .sugarjar.yaml.
         | 
| 130 152 |  | 
| 153 | 
            +
              pullsuggestions, ps
         | 
| 154 | 
            +
                          Pull any suggestions *that have been committed* in the GitHub UI.
         | 
| 155 | 
            +
                          This will show the diff and prompt for confirmation before
         | 
| 156 | 
            +
                          merging. Note that a fast-forward merge will be used.
         | 
| 157 | 
            +
             | 
| 131 158 | 
             
              smartclone, sclone
         | 
| 132 159 | 
             
                          A smart wrapper to "git clone" that handles forking and managing
         | 
| 133 160 | 
             
                          remotes for you.
         | 
| @@ -162,6 +189,10 @@ COMMANDS: | |
| 162 189 | 
             
              version
         | 
| 163 190 | 
             
                          Print the version of sugarjar, and then run 'hub version'
         | 
| 164 191 | 
             
                          to show the hub and git versions.
         | 
| 192 | 
            +
             | 
| 193 | 
            +
            Be sure to checkout Sapling (https://sapling-scm.com/)! This was written as
         | 
| 194 | 
            +
            a stop-gap to get Sapling features before it was open-sourced, and as such
         | 
| 195 | 
            +
            I may deprecate this in the future.
         | 
| 165 196 | 
             
            COMMANDS
         | 
| 166 197 |  | 
| 167 198 | 
             
              # rubocop:enable Layout/HeredocIndentation
         | 
| @@ -232,7 +263,9 @@ else | |
| 232 263 | 
             
              end
         | 
| 233 264 | 
             
            end
         | 
| 234 265 |  | 
| 235 | 
            -
             | 
| 266 | 
            +
            subcommand = argv_copy.reject { |x| x.start_with?('-') }.first
         | 
| 267 | 
            +
             | 
| 268 | 
            +
            if ARGV.empty? || !subcommand
         | 
| 236 269 | 
             
              puts parser
         | 
| 237 270 | 
             
              exit
         | 
| 238 271 | 
             
            end
         | 
| @@ -243,7 +276,6 @@ options = config.merge(options) | |
| 243 276 | 
             
            SugarJar::Log.level = options['log_level'].to_sym if options['log_level']
         | 
| 244 277 | 
             
            sj = SugarJar::Commands.new(options)
         | 
| 245 278 |  | 
| 246 | 
            -
            subcommand = argv_copy.reject { |x| x.start_with?('-') }.first
         | 
| 247 279 | 
             
            is_valid_command = valid_commands.include?(subcommand.to_sym)
         | 
| 248 280 | 
             
            argv_copy.delete(subcommand)
         | 
| 249 281 | 
             
            SugarJar::Log.debug("subcommand is #{subcommand}")
         | 
| @@ -265,7 +297,13 @@ if is_valid_command | |
| 265 297 | 
             
              sj.send(subcommand.to_sym, *extra_opts)
         | 
| 266 298 | 
             
            elsif options['fallthru']
         | 
| 267 299 | 
             
              SugarJar::Log.debug("Falling thru to: hub #{ARGV.join(' ')}")
         | 
| 268 | 
            -
               | 
| 300 | 
            +
              if options['github_cli'] == 'hub'
         | 
| 301 | 
            +
                exec('hub', *ARGV)
         | 
| 302 | 
            +
              else
         | 
| 303 | 
            +
                # If we're using 'gh', it doesn't have 'git fall thru' support, so
         | 
| 304 | 
            +
                # we pass thru directly to 'git'
         | 
| 305 | 
            +
                exec('git', *ARGV)
         | 
| 306 | 
            +
              end
         | 
| 269 307 | 
             
            else
         | 
| 270 308 | 
             
              SugarJar::Log.error("No such subcommand: #{subcommand}")
         | 
| 271 309 | 
             
            end
         | 
    
        data/lib/sugarjar/commands.rb
    CHANGED
    
    | @@ -22,20 +22,29 @@ class SugarJar | |
| 22 22 | 
             
                  @ignore_prerun_failure = options['ignore_prerun_failure']
         | 
| 23 23 | 
             
                  @repo_config = SugarJar::RepoConfig.config
         | 
| 24 24 | 
             
                  @color = options['color']
         | 
| 25 | 
            +
                  @feature_prefix = options['feature_prefix']
         | 
| 26 | 
            +
                  @checks = {}
         | 
| 27 | 
            +
                  @main_branch = nil
         | 
| 28 | 
            +
                  @main_remote_branches = {}
         | 
| 25 29 | 
             
                  return if options['no_change']
         | 
| 26 30 |  | 
| 27 | 
            -
                   | 
| 31 | 
            +
                  # technically this doesn't "change" things, but we won't have this
         | 
| 32 | 
            +
                  # option on the no_change call
         | 
| 33 | 
            +
                  @cli = determine_cli(options['github_cli'])
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  set_hub_host
         | 
| 28 36 | 
             
                  set_commit_template if @repo_config['commit_template']
         | 
| 29 37 | 
             
                end
         | 
| 30 38 |  | 
| 31 39 | 
             
                def feature(name, base = nil)
         | 
| 32 40 | 
             
                  assert_in_repo
         | 
| 33 41 | 
             
                  SugarJar::Log.debug("Feature: #{name}, #{base}")
         | 
| 34 | 
            -
                   | 
| 42 | 
            +
                  name = fprefix(name)
         | 
| 43 | 
            +
                  die("#{name} already exists!") if all_local_branches.include?(name)
         | 
| 35 44 | 
             
                  base ||= most_main
         | 
| 36 45 | 
             
                  base_pieces = base.split('/')
         | 
| 37 | 
            -
                   | 
| 38 | 
            -
                   | 
| 46 | 
            +
                  git('fetch', base_pieces[0]) if base_pieces.length > 1
         | 
| 47 | 
            +
                  git('checkout', '-b', name, base)
         | 
| 39 48 | 
             
                  SugarJar::Log.info(
         | 
| 40 49 | 
             
                    "Created feature branch #{color(name, :green)} based on " +
         | 
| 41 50 | 
             
                    color(base, :green),
         | 
| @@ -45,6 +54,7 @@ class SugarJar | |
| 45 54 | 
             
                def bclean(name = nil)
         | 
| 46 55 | 
             
                  assert_in_repo
         | 
| 47 56 | 
             
                  name ||= current_branch
         | 
| 57 | 
            +
                  name = fprefix(name) unless all_local_branches.include?(name)
         | 
| 48 58 | 
             
                  if clean_branch(name)
         | 
| 49 59 | 
             
                    SugarJar::Log.info("#{name}: #{color('reaped', :green)}")
         | 
| 50 60 | 
             
                  else
         | 
| @@ -58,7 +68,7 @@ class SugarJar | |
| 58 68 | 
             
                def bcleanall
         | 
| 59 69 | 
             
                  assert_in_repo
         | 
| 60 70 | 
             
                  curr = current_branch
         | 
| 61 | 
            -
                   | 
| 71 | 
            +
                  all_local_branches.each do |branch|
         | 
| 62 72 | 
             
                    if MAIN_BRANCHES.include?(branch)
         | 
| 63 73 | 
             
                      SugarJar::Log.debug("Skipping #{branch}")
         | 
| 64 74 | 
             
                      next
         | 
| @@ -76,8 +86,8 @@ class SugarJar | |
| 76 86 | 
             
                  end
         | 
| 77 87 |  | 
| 78 88 | 
             
                  # Return to the branch we were on, or main
         | 
| 79 | 
            -
                  if  | 
| 80 | 
            -
                     | 
| 89 | 
            +
                  if all_local_branches.include?(curr)
         | 
| 90 | 
            +
                    git('checkout', curr)
         | 
| 81 91 | 
             
                  else
         | 
| 82 92 | 
             
                    checkout_main_branch
         | 
| 83 93 | 
             
                  end
         | 
| @@ -85,18 +95,18 @@ class SugarJar | |
| 85 95 |  | 
| 86 96 | 
             
                def co(*args)
         | 
| 87 97 | 
             
                  assert_in_repo
         | 
| 88 | 
            -
                  s =  | 
| 98 | 
            +
                  s = git('checkout', *args)
         | 
| 89 99 | 
             
                  SugarJar::Log.info(s.stderr + s.stdout.chomp)
         | 
| 90 100 | 
             
                end
         | 
| 91 101 |  | 
| 92 102 | 
             
                def br
         | 
| 93 103 | 
             
                  assert_in_repo
         | 
| 94 | 
            -
                  SugarJar::Log.info( | 
| 104 | 
            +
                  SugarJar::Log.info(git('branch', '-v').stdout.chomp)
         | 
| 95 105 | 
             
                end
         | 
| 96 106 |  | 
| 97 107 | 
             
                def binfo
         | 
| 98 108 | 
             
                  assert_in_repo
         | 
| 99 | 
            -
                  SugarJar::Log.info( | 
| 109 | 
            +
                  SugarJar::Log.info(git(
         | 
| 100 110 | 
             
                    'log', '--graph', '--oneline', '--decorate', '--boundary',
         | 
| 101 111 | 
             
                    "#{tracked_branch}.."
         | 
| 102 112 | 
             
                  ).stdout.chomp)
         | 
| @@ -105,7 +115,7 @@ class SugarJar | |
| 105 115 | 
             
                # binfo for all branches
         | 
| 106 116 | 
             
                def smartlog
         | 
| 107 117 | 
             
                  assert_in_repo
         | 
| 108 | 
            -
                  SugarJar::Log.info( | 
| 118 | 
            +
                  SugarJar::Log.info(git(
         | 
| 109 119 | 
             
                    'log', '--graph', '--oneline', '--decorate', '--boundary',
         | 
| 110 120 | 
             
                    '--branches', "#{most_main}.."
         | 
| 111 121 | 
             
                  ).stdout.chomp)
         | 
| @@ -142,24 +152,24 @@ class SugarJar | |
| 142 152 |  | 
| 143 153 | 
             
                def qamend(*args)
         | 
| 144 154 | 
             
                  assert_in_repo
         | 
| 145 | 
            -
                  SugarJar::Log.info( | 
| 155 | 
            +
                  SugarJar::Log.info(git('commit', '--amend', '--no-edit', *args).stdout)
         | 
| 146 156 | 
             
                end
         | 
| 147 157 |  | 
| 148 158 | 
             
                alias amendq qamend
         | 
| 149 159 |  | 
| 150 160 | 
             
                def upall
         | 
| 151 161 | 
             
                  assert_in_repo
         | 
| 152 | 
            -
                   | 
| 162 | 
            +
                  all_local_branches.each do |branch|
         | 
| 153 163 | 
             
                    next if MAIN_BRANCHES.include?(branch)
         | 
| 154 164 |  | 
| 155 | 
            -
                     | 
| 165 | 
            +
                    git('checkout', branch)
         | 
| 156 166 | 
             
                    result = gitup
         | 
| 157 167 | 
             
                    if result['so'].error?
         | 
| 158 168 | 
             
                      SugarJar::Log.error(
         | 
| 159 169 | 
             
                        "#{color(branch, :red)} failed rebase. Reverting attempt and " +
         | 
| 160 170 | 
             
                        'moving to next branch. Try `sj up` manually on that branch.',
         | 
| 161 171 | 
             
                      )
         | 
| 162 | 
            -
                       | 
| 172 | 
            +
                      git('rebase', '--abort')
         | 
| 163 173 | 
             
                    else
         | 
| 164 174 | 
             
                      SugarJar::Log.info(
         | 
| 165 175 | 
             
                        "#{color(branch, :green)} rebased on " +
         | 
| @@ -176,21 +186,42 @@ class SugarJar | |
| 176 186 |  | 
| 177 187 | 
             
                  reponame = File.basename(repo, '.git')
         | 
| 178 188 | 
             
                  dir ||= reponame
         | 
| 189 | 
            +
                  org = extract_org(repo)
         | 
| 190 | 
            +
             | 
| 179 191 | 
             
                  SugarJar::Log.info("Cloning #{reponame}...")
         | 
| 180 | 
            -
                  hub('clone', canonicalize_repo(repo), dir, *args)
         | 
| 181 192 |  | 
| 193 | 
            +
                  # GH's 'fork' command (with the --clone arg) will fork, if necessary,
         | 
| 194 | 
            +
                  # then clone, and then setup the remotes with the appropriate names. So
         | 
| 195 | 
            +
                  # we just let it do all the work for us and return.
         | 
| 196 | 
            +
                  #
         | 
| 197 | 
            +
                  # Unless the repo is in our own org and cannot be forked, then it
         | 
| 198 | 
            +
                  # will fail.
         | 
| 199 | 
            +
                  if gh? && org != @ghuser
         | 
| 200 | 
            +
                    ghcli('repo', 'fork', '--clone', canonicalize_repo(repo), dir, *args)
         | 
| 201 | 
            +
                    SugarJar::Log.info('Remotes "origin" and "upstream" configured.')
         | 
| 202 | 
            +
                    return
         | 
| 203 | 
            +
                  end
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                  # For 'hub' first we clone, using git, as 'hub' always needs a repo to
         | 
| 206 | 
            +
                  # operate on.
         | 
| 207 | 
            +
                  #
         | 
| 208 | 
            +
                  # Or for 'gh' when we can't fork...
         | 
| 209 | 
            +
                  git('clone', canonicalize_repo(repo), dir, *args)
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                  # Then we go into it and attempt to use the 'fork' capability
         | 
| 212 | 
            +
                  # or if not
         | 
| 182 213 | 
             
                  Dir.chdir dir do
         | 
| 183 214 | 
             
                    # Now that we have a repo, if we have a hub host set it.
         | 
| 184 | 
            -
                    set_hub_host | 
| 215 | 
            +
                    set_hub_host
         | 
| 185 216 |  | 
| 186 | 
            -
                    org = extract_org(repo)
         | 
| 187 217 | 
             
                    SugarJar::Log.debug("Comparing org #{org} to ghuser #{@ghuser}")
         | 
| 188 218 | 
             
                    if org == @ghuser
         | 
| 189 219 | 
             
                      puts 'Cloned forked or self-owned repo. Not creating "upstream".'
         | 
| 220 | 
            +
                      SugarJar::Log.info('Remotes "origin" and "upstream" configured.')
         | 
| 190 221 | 
             
                      return
         | 
| 191 222 | 
             
                    end
         | 
| 192 223 |  | 
| 193 | 
            -
                    s =  | 
| 224 | 
            +
                    s = ghcli_nofail('repo', 'fork', '--remote-name=origin')
         | 
| 194 225 | 
             
                    if s.error?
         | 
| 195 226 | 
             
                      if s.stdout.include?('SAML enforcement')
         | 
| 196 227 | 
             
                        SugarJar::Log.info(
         | 
| @@ -199,18 +230,18 @@ class SugarJar | |
| 199 230 | 
             
                        )
         | 
| 200 231 | 
             
                        exit(1)
         | 
| 201 232 | 
             
                      else
         | 
| 202 | 
            -
                        #  | 
| 203 | 
            -
                        # already existed. If we got an error, but didn't recognize
         | 
| 204 | 
            -
                        # that, we'll assume that's what happened and try to add the
         | 
| 205 | 
            -
                        #  | 
| 233 | 
            +
                        # gh as well as old versions of hub, it would fail if the upstream
         | 
| 234 | 
            +
                        # fork already existed. If we got an error, but didn't recognize
         | 
| 235 | 
            +
                        # that, we'll assume that's what happened and try to add the remote
         | 
| 236 | 
            +
                        # ourselves.
         | 
| 206 237 | 
             
                        SugarJar::Log.info("Fork (#{@ghuser}/#{reponame}) detected.")
         | 
| 207 238 | 
             
                        SugarJar::Log.debug(
         | 
| 208 239 | 
             
                          'The above is a bit of a lie. "hub" failed to fork and it was ' +
         | 
| 209 240 | 
             
                          'not a SAML error, so our best guess is that a fork exists ' +
         | 
| 210 241 | 
             
                          'and so we will try to configure it.',
         | 
| 211 242 | 
             
                        )
         | 
| 212 | 
            -
                         | 
| 213 | 
            -
                         | 
| 243 | 
            +
                        git('remote', 'rename', 'origin', 'upstream')
         | 
| 244 | 
            +
                        git('remote', 'add', 'origin', forked_repo(repo, @ghuser))
         | 
| 214 245 | 
             
                      end
         | 
| 215 246 | 
             
                    else
         | 
| 216 247 | 
             
                      SugarJar::Log.info("Forked #{reponame} to #{@ghuser}")
         | 
| @@ -247,11 +278,15 @@ class SugarJar | |
| 247 278 |  | 
| 248 279 | 
             
                def version
         | 
| 249 280 | 
             
                  puts "sugarjar version #{SugarJar::VERSION}"
         | 
| 250 | 
            -
                  puts  | 
| 281 | 
            +
                  puts ghcli('version').stdout
         | 
| 282 | 
            +
                  # 'hub' prints the 'git' version, but gh doesn't, so if we're on 'gh'
         | 
| 283 | 
            +
                  # print out the git version directly
         | 
| 284 | 
            +
                  puts git('version').stdout if gh?
         | 
| 251 285 | 
             
                end
         | 
| 252 286 |  | 
| 253 | 
            -
                def smartpullrequest
         | 
| 287 | 
            +
                def smartpullrequest(*args)
         | 
| 254 288 | 
             
                  assert_in_repo
         | 
| 289 | 
            +
                  assert_common_main_branch
         | 
| 255 290 | 
             
                  if dirty?
         | 
| 256 291 | 
             
                    SugarJar::Log.warn(
         | 
| 257 292 | 
             
                      'Your repo is dirty, so I am not going to create a pull request. ' +
         | 
| @@ -259,14 +294,73 @@ class SugarJar | |
| 259 294 | 
             
                    )
         | 
| 260 295 | 
             
                    exit(1)
         | 
| 261 296 | 
             
                  end
         | 
| 262 | 
            -
                   | 
| 297 | 
            +
                  if gh?
         | 
| 298 | 
            +
                    SugarJar::Log.trace("Running: gh pr create #{args.join(' ')}")
         | 
| 299 | 
            +
                    system(which('gh'), 'pr', 'create', *args)
         | 
| 300 | 
            +
                  else
         | 
| 301 | 
            +
                    SugarJar::Log.trace("Running: hub pull-request #{args.join(' ')}")
         | 
| 302 | 
            +
                    system(which('hub'), 'pull-request', *args)
         | 
| 303 | 
            +
                  end
         | 
| 263 304 | 
             
                end
         | 
| 264 305 |  | 
| 265 306 | 
             
                alias spr smartpullrequest
         | 
| 266 307 | 
             
                alias smartpr smartpullrequest
         | 
| 267 308 |  | 
| 309 | 
            +
                def pullsuggestions
         | 
| 310 | 
            +
                  assert_in_repo
         | 
| 311 | 
            +
             | 
| 312 | 
            +
                  if dirty?
         | 
| 313 | 
            +
                    if @ignore_dirty
         | 
| 314 | 
            +
                      SugarJar::Log.warn(
         | 
| 315 | 
            +
                        'Your repo is dirty, but --ignore-dirty was specified, so ' +
         | 
| 316 | 
            +
                        'carrying on anyway.',
         | 
| 317 | 
            +
                      )
         | 
| 318 | 
            +
                    else
         | 
| 319 | 
            +
                      SugarJar::Log.error(
         | 
| 320 | 
            +
                        'Your repo is dirty, so I am not going to push. Please commit ' +
         | 
| 321 | 
            +
                        'or amend first.',
         | 
| 322 | 
            +
                      )
         | 
| 323 | 
            +
                      exit(1)
         | 
| 324 | 
            +
                    end
         | 
| 325 | 
            +
                  end
         | 
| 326 | 
            +
             | 
| 327 | 
            +
                  src = "origin/#{current_branch}"
         | 
| 328 | 
            +
                  fetch('origin')
         | 
| 329 | 
            +
                  diff = git('diff', src).stdout
         | 
| 330 | 
            +
                  return unless diff && !diff.empty?
         | 
| 331 | 
            +
             | 
| 332 | 
            +
                  puts "Will merge the following suggestions:\n\n#{diff}"
         | 
| 333 | 
            +
             | 
| 334 | 
            +
                  loop do
         | 
| 335 | 
            +
                    $stdout.print("\nAre you sure? [y/n] ")
         | 
| 336 | 
            +
                    ans = $stdin.gets.strip
         | 
| 337 | 
            +
                    case ans
         | 
| 338 | 
            +
                    when /^[Yy]$/
         | 
| 339 | 
            +
                      system(which('git'), 'merge', '--ff', "origin/#{current_branch}")
         | 
| 340 | 
            +
                      break
         | 
| 341 | 
            +
                    when /^[Nn]$/, /^[Qq](uit)?/
         | 
| 342 | 
            +
                      puts 'Not merging at user request...'
         | 
| 343 | 
            +
                      break
         | 
| 344 | 
            +
                    else
         | 
| 345 | 
            +
                      puts "Didn't understand '#{ans}'."
         | 
| 346 | 
            +
                    end
         | 
| 347 | 
            +
                  end
         | 
| 348 | 
            +
                end
         | 
| 349 | 
            +
             | 
| 350 | 
            +
                alias ps pullsuggestions
         | 
| 351 | 
            +
             | 
| 268 352 | 
             
                private
         | 
| 269 353 |  | 
| 354 | 
            +
                def fprefix(name)
         | 
| 355 | 
            +
                  return name unless @feature_prefix
         | 
| 356 | 
            +
             | 
| 357 | 
            +
                  newname = "#{@feature_prefix}#{name}"
         | 
| 358 | 
            +
                  SugarJar::Log.debug(
         | 
| 359 | 
            +
                    "Munging feature name: #{name} -> #{newname} due to feature prefix",
         | 
| 360 | 
            +
                  )
         | 
| 361 | 
            +
                  newname
         | 
| 362 | 
            +
                end
         | 
| 363 | 
            +
             | 
| 270 364 | 
             
                def _smartpush(remote, branch, force)
         | 
| 271 365 | 
             
                  unless remote && branch
         | 
| 272 366 | 
             
                    remote ||= 'origin'
         | 
| @@ -302,11 +396,11 @@ class SugarJar | |
| 302 396 |  | 
| 303 397 | 
             
                  args = ['push', remote, branch]
         | 
| 304 398 | 
             
                  args << '--force-with-lease' if force
         | 
| 305 | 
            -
                  puts  | 
| 399 | 
            +
                  puts git(*args).stderr
         | 
| 306 400 | 
             
                end
         | 
| 307 401 |  | 
| 308 402 | 
             
                def dirty?
         | 
| 309 | 
            -
                  s =  | 
| 403 | 
            +
                  s = git_nofail('diff', '--quiet')
         | 
| 310 404 | 
             
                  s.error?
         | 
| 311 405 | 
             
                end
         | 
| 312 406 |  | 
| @@ -343,9 +437,9 @@ class SugarJar | |
| 343 437 | 
             
                end
         | 
| 344 438 |  | 
| 345 439 | 
             
                def set_hub_host
         | 
| 346 | 
            -
                  return unless in_repo
         | 
| 440 | 
            +
                  return unless hub? && in_repo && @ghhost
         | 
| 347 441 |  | 
| 348 | 
            -
                  s =  | 
| 442 | 
            +
                  s = git_nofail('config', '--local', '--get', 'hub.host')
         | 
| 349 443 | 
             
                  if s.error?
         | 
| 350 444 | 
             
                    SugarJar::Log.info("Setting repo hub.host = #{@ghhost}")
         | 
| 351 445 | 
             
                  else
         | 
| @@ -363,7 +457,7 @@ class SugarJar | |
| 363 457 | 
             
                    end
         | 
| 364 458 | 
             
                    return
         | 
| 365 459 | 
             
                  end
         | 
| 366 | 
            -
                   | 
| 460 | 
            +
                  git('config', '--local', '--add', 'hub.host', @ghhost)
         | 
| 367 461 | 
             
                end
         | 
| 368 462 |  | 
| 369 463 | 
             
                def set_commit_template
         | 
| @@ -384,7 +478,7 @@ class SugarJar | |
| 384 478 | 
             
                    )
         | 
| 385 479 | 
             
                  end
         | 
| 386 480 |  | 
| 387 | 
            -
                  s =  | 
| 481 | 
            +
                  s = git_nofail('config', '--local', 'commit.template')
         | 
| 388 482 | 
             
                  unless s.error?
         | 
| 389 483 | 
             
                    current = s.stdout.strip
         | 
| 390 484 | 
             
                    if current == @repo_config['commit_template']
         | 
| @@ -402,18 +496,60 @@ class SugarJar | |
| 402 496 | 
             
                    'Setting repo-specific commit template to ' +
         | 
| 403 497 | 
             
                    "#{@repo_config['commit_template']} per sugarjar repo config.",
         | 
| 404 498 | 
             
                  )
         | 
| 405 | 
            -
                   | 
| 499 | 
            +
                  git(
         | 
| 406 500 | 
             
                    'config', '--local', 'commit.template', @repo_config['commit_template']
         | 
| 407 501 | 
             
                  )
         | 
| 408 502 | 
             
                end
         | 
| 409 503 |  | 
| 410 | 
            -
                def  | 
| 411 | 
            -
                  unless @repo_config[type]
         | 
| 412 | 
            -
             | 
| 413 | 
            -
             | 
| 504 | 
            +
                def get_checks_from_command(type)
         | 
| 505 | 
            +
                  return nil unless @repo_config["#{type}_list_cmd"]
         | 
| 506 | 
            +
             | 
| 507 | 
            +
                  cmd = @repo_config["#{type}_list_cmd"]
         | 
| 508 | 
            +
                  short = cmd.split.first
         | 
| 509 | 
            +
                  unless File.exist?(short)
         | 
| 510 | 
            +
                    SugarJar::Log.error(
         | 
| 511 | 
            +
                      "Configured #{type}_list_cmd #{short} does not exist!",
         | 
| 512 | 
            +
                    )
         | 
| 513 | 
            +
                    return false
         | 
| 514 | 
            +
                  end
         | 
| 515 | 
            +
                  s = Mixlib::ShellOut.new(cmd).run_command
         | 
| 516 | 
            +
                  if s.error?
         | 
| 517 | 
            +
                    SugarJar::Log.error(
         | 
| 518 | 
            +
                      "#{type}_list_cmd (#{cmd}) failed: #{s.format_for_exception}",
         | 
| 519 | 
            +
                    )
         | 
| 520 | 
            +
                    return false
         | 
| 521 | 
            +
                  end
         | 
| 522 | 
            +
                  s.stdout.split("\n")
         | 
| 523 | 
            +
                end
         | 
| 524 | 
            +
             | 
| 525 | 
            +
                # determine if we're using the _list_cmd and if so run it to get the
         | 
| 526 | 
            +
                # checks, or just use the directly-defined check, and cache it
         | 
| 527 | 
            +
                def get_checks(type)
         | 
| 528 | 
            +
                  return @checks[type] if @checks[type]
         | 
| 529 | 
            +
             | 
| 530 | 
            +
                  ret = get_checks_from_command(type)
         | 
| 531 | 
            +
                  if ret
         | 
| 532 | 
            +
                    SugarJar::Log.debug("Found #{type}s: #{ret}")
         | 
| 533 | 
            +
                    @checks[type] = ret
         | 
| 534 | 
            +
                  # if it's explicitly false, we failed to run the command
         | 
| 535 | 
            +
                  elsif ret == false
         | 
| 536 | 
            +
                    @checks[type] = false
         | 
| 537 | 
            +
                  # otherwise, we move on (basically: it's nil, there was no _list_cmd)
         | 
| 538 | 
            +
                  else
         | 
| 539 | 
            +
                    SugarJar::Log.debug("[#{type}]: using listed linters: #{ret}")
         | 
| 540 | 
            +
                    @checks[type] = @repo_config[type] || []
         | 
| 414 541 | 
             
                  end
         | 
| 542 | 
            +
                  @checks[type]
         | 
| 543 | 
            +
                end
         | 
| 544 | 
            +
             | 
| 545 | 
            +
                def run_check(type)
         | 
| 415 546 | 
             
                  Dir.chdir repo_root do
         | 
| 416 | 
            -
                     | 
| 547 | 
            +
                    checks = get_checks(type)
         | 
| 548 | 
            +
                    # if we failed to determine the checks, the the checks have effectively
         | 
| 549 | 
            +
                    # failed
         | 
| 550 | 
            +
                    return false unless checks
         | 
| 551 | 
            +
             | 
| 552 | 
            +
                    checks.each do |check|
         | 
| 417 553 | 
             
                      SugarJar::Log.debug("Running #{type} #{check}")
         | 
| 418 554 |  | 
| 419 555 | 
             
                      short = check.split.first
         | 
| @@ -431,7 +567,7 @@ class SugarJar | |
| 431 567 | 
             
                        SugarJar::Log.warn(
         | 
| 432 568 | 
             
                          "The linter modified the repo. Here's the diff:\n",
         | 
| 433 569 | 
             
                        )
         | 
| 434 | 
            -
                        puts  | 
| 570 | 
            +
                        puts git('diff').stdout
         | 
| 435 571 | 
             
                        loop do
         | 
| 436 572 | 
             
                          $stdout.print(
         | 
| 437 573 | 
             
                            "\nWould you like to\n\t[q]uit and inspect\n\t[a]mend the " +
         | 
| @@ -483,16 +619,53 @@ class SugarJar | |
| 483 619 | 
             
                  exit(1)
         | 
| 484 620 | 
             
                end
         | 
| 485 621 |  | 
| 622 | 
            +
                def assert_common_main_branch
         | 
| 623 | 
            +
                  upstream_branch = main_remote_branch(upstream)
         | 
| 624 | 
            +
                  unless main_branch == upstream_branch
         | 
| 625 | 
            +
                    die(
         | 
| 626 | 
            +
                      "The local main branch is '#{main_branch}', but the main branch " +
         | 
| 627 | 
            +
                      "of the #{upstream} remote is '#{upstream_branch}'. You probably " +
         | 
| 628 | 
            +
                      "want to rename your local branch by doing:\n\t" +
         | 
| 629 | 
            +
                      "git branch -m #{main_branch} #{upstream_branch}\n\t" +
         | 
| 630 | 
            +
                      "git fetch #{upstream}\n\t" +
         | 
| 631 | 
            +
                      "git branch -u #{upstream}/#{upstream_branch} #{upstream_branch}\n" +
         | 
| 632 | 
            +
                      "\tgit remote set-head #{upstream} -a",
         | 
| 633 | 
            +
                    )
         | 
| 634 | 
            +
                  end
         | 
| 635 | 
            +
                  return if upstream_branch == 'origin'
         | 
| 636 | 
            +
             | 
| 637 | 
            +
                  origin_branch = main_remote_branch('origin')
         | 
| 638 | 
            +
                  return if origin_branch == upstream_branch
         | 
| 639 | 
            +
             | 
| 640 | 
            +
                  die(
         | 
| 641 | 
            +
                    "The main branch of your upstream (#{upstream_branch}) and your " +
         | 
| 642 | 
            +
                    "fork/origin (#{origin_branch}) are not the same. You should go " +
         | 
| 643 | 
            +
                    "to https://#{@ghhost || 'github.com'}/#{@ghuser}/#{repo_name}/" +
         | 
| 644 | 
            +
                    'branches/ and rename the \'default\' branch to ' +
         | 
| 645 | 
            +
                    "'#{upstream_branch}'. It will then give you some commands to " +
         | 
| 646 | 
            +
                    'run to update this clone.',
         | 
| 647 | 
            +
                  )
         | 
| 648 | 
            +
                end
         | 
| 649 | 
            +
             | 
| 486 650 | 
             
                def assert_in_repo
         | 
| 487 651 | 
             
                  die('sugarjar must be run from inside a git repo') unless in_repo
         | 
| 488 652 | 
             
                end
         | 
| 489 653 |  | 
| 654 | 
            +
                def determine_main_branch(branches)
         | 
| 655 | 
            +
                  branches.include?('main') ? 'main' : 'master'
         | 
| 656 | 
            +
                end
         | 
| 657 | 
            +
             | 
| 490 658 | 
             
                def main_branch
         | 
| 491 | 
            -
                  @main_branch =  | 
| 659 | 
            +
                  @main_branch = determine_main_branch(all_local_branches)
         | 
| 660 | 
            +
                end
         | 
| 661 | 
            +
             | 
| 662 | 
            +
                def main_remote_branch(remote)
         | 
| 663 | 
            +
                  @main_remote_branches[remote] ||=
         | 
| 664 | 
            +
                    determine_main_branch(all_remote_branches(remote))
         | 
| 492 665 | 
             
                end
         | 
| 493 666 |  | 
| 494 667 | 
             
                def checkout_main_branch
         | 
| 495 | 
            -
                   | 
| 668 | 
            +
                  git('checkout', main_branch)
         | 
| 496 669 | 
             
                end
         | 
| 497 670 |  | 
| 498 671 | 
             
                def clean_branch(name)
         | 
| @@ -503,15 +676,25 @@ class SugarJar | |
| 503 676 |  | 
| 504 677 | 
             
                  SugarJar::Log.debug('branch deemed safe to delete...')
         | 
| 505 678 | 
             
                  checkout_main_branch
         | 
| 506 | 
            -
                   | 
| 679 | 
            +
                  git('branch', '-D', name)
         | 
| 507 680 | 
             
                  gitup
         | 
| 508 681 | 
             
                  true
         | 
| 509 682 | 
             
                end
         | 
| 510 683 |  | 
| 511 | 
            -
                def  | 
| 684 | 
            +
                def all_remote_branches(remote = 'origin')
         | 
| 685 | 
            +
                  branches = []
         | 
| 686 | 
            +
                  git('branch', '-r', '--format', '%(refname)').stdout.lines.each do |line|
         | 
| 687 | 
            +
                    next unless line.start_with?("refs/remotes/#{remote}/")
         | 
| 688 | 
            +
             | 
| 689 | 
            +
                    branches << branch_from_ref(line.strip, :remote)
         | 
| 690 | 
            +
                  end
         | 
| 691 | 
            +
                  branches
         | 
| 692 | 
            +
                end
         | 
| 693 | 
            +
             | 
| 694 | 
            +
                def all_local_branches
         | 
| 512 695 | 
             
                  branches = []
         | 
| 513 | 
            -
                   | 
| 514 | 
            -
                    branches << line.strip | 
| 696 | 
            +
                  git('branch', '--format', '%(refname)').stdout.lines.each do |line|
         | 
| 697 | 
            +
                    branches << branch_from_ref(line.strip)
         | 
| 515 698 | 
             
                  end
         | 
| 516 699 | 
             
                  branches
         | 
| 517 700 | 
             
                end
         | 
| @@ -520,12 +703,12 @@ class SugarJar | |
| 520 703 | 
             
                  # cherry -v will output 1 line per commit on the target branch
         | 
| 521 704 | 
             
                  # prefixed by a - or + - anything with a - can be dropped, anything
         | 
| 522 705 | 
             
                  # else cannot.
         | 
| 523 | 
            -
                  out =  | 
| 706 | 
            +
                  out = git(
         | 
| 524 707 | 
             
                    'cherry', '-v', tracked_branch, branch
         | 
| 525 708 | 
             
                  ).stdout.lines.reject do |line|
         | 
| 526 709 | 
             
                    line.start_with?('-')
         | 
| 527 710 | 
             
                  end
         | 
| 528 | 
            -
                  if out. | 
| 711 | 
            +
                  if out.empty?
         | 
| 529 712 | 
             
                    SugarJar::Log.debug(
         | 
| 530 713 | 
             
                      "cherry-pick shows branch #{branch} obviously safe to delete",
         | 
| 531 714 | 
             
                    )
         | 
| @@ -539,8 +722,8 @@ class SugarJar | |
| 539 722 | 
             
                  # First we need a temp branch to work on
         | 
| 540 723 | 
             
                  tmpbranch = "_sugar_jar.#{Process.pid}"
         | 
| 541 724 |  | 
| 542 | 
            -
                   | 
| 543 | 
            -
                  s =  | 
| 725 | 
            +
                  git('checkout', '-b', tmpbranch, tracked_branch)
         | 
| 726 | 
            +
                  s = git_nofail('merge', '--squash', branch)
         | 
| 544 727 | 
             
                  if s.error?
         | 
| 545 728 | 
             
                    cleanup_tmp_branch(tmpbranch, branch)
         | 
| 546 729 | 
             
                    SugarJar::Log.debug(
         | 
| @@ -551,7 +734,7 @@ class SugarJar | |
| 551 734 | 
             
                    return false
         | 
| 552 735 | 
             
                  end
         | 
| 553 736 |  | 
| 554 | 
            -
                  s =  | 
| 737 | 
            +
                  s = git('diff', '--staged')
         | 
| 555 738 | 
             
                  out = s.stdout
         | 
| 556 739 | 
             
                  SugarJar::Log.debug("Squash-merged diff: #{out}")
         | 
| 557 740 | 
             
                  cleanup_tmp_branch(tmpbranch, branch)
         | 
| @@ -569,18 +752,22 @@ class SugarJar | |
| 569 752 | 
             
                end
         | 
| 570 753 |  | 
| 571 754 | 
             
                def cleanup_tmp_branch(tmp, backto)
         | 
| 572 | 
            -
                   | 
| 573 | 
            -
                   | 
| 574 | 
            -
                   | 
| 755 | 
            +
                  git('reset', '--hard', tracked_branch)
         | 
| 756 | 
            +
                  git('checkout', backto)
         | 
| 757 | 
            +
                  git('branch', '-D', tmp)
         | 
| 575 758 | 
             
                end
         | 
| 576 759 |  | 
| 577 760 | 
             
                def current_branch
         | 
| 578 | 
            -
                   | 
| 761 | 
            +
                  branch_from_ref(git('symbolic-ref', 'HEAD').stdout.strip)
         | 
| 579 762 | 
             
                end
         | 
| 580 763 |  | 
| 581 764 | 
             
                def fetch_upstream
         | 
| 582 765 | 
             
                  us = upstream
         | 
| 583 | 
            -
                   | 
| 766 | 
            +
                  fetch(us) if us
         | 
| 767 | 
            +
                end
         | 
| 768 | 
            +
             | 
| 769 | 
            +
                def fetch(remote)
         | 
| 770 | 
            +
                  git('fetch', remote)
         | 
| 584 771 | 
             
                end
         | 
| 585 772 |  | 
| 586 773 | 
             
                def gitup
         | 
| @@ -598,7 +785,7 @@ class SugarJar | |
| 598 785 | 
             
                    )
         | 
| 599 786 | 
             
                  end
         | 
| 600 787 | 
             
                  SugarJar::Log.debug('Rebasing')
         | 
| 601 | 
            -
                  s =  | 
| 788 | 
            +
                  s = git_nofail('rebase', base)
         | 
| 602 789 | 
             
                  {
         | 
| 603 790 | 
             
                    'so' => s,
         | 
| 604 791 | 
             
                    'base' => base,
         | 
| @@ -606,7 +793,7 @@ class SugarJar | |
| 606 793 | 
             
                end
         | 
| 607 794 |  | 
| 608 795 | 
             
                def tracked_branch
         | 
| 609 | 
            -
                  s =  | 
| 796 | 
            +
                  s = git_nofail(
         | 
| 610 797 | 
             
                    'rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'
         | 
| 611 798 | 
             
                  )
         | 
| 612 799 | 
             
                  if s.error?
         | 
| @@ -628,7 +815,7 @@ class SugarJar | |
| 628 815 | 
             
                def upstream
         | 
| 629 816 | 
             
                  return @remote if @remote
         | 
| 630 817 |  | 
| 631 | 
            -
                  s =  | 
| 818 | 
            +
                  s = git('remote')
         | 
| 632 819 |  | 
| 633 820 | 
             
                  remotes = s.stdout.lines.map(&:strip)
         | 
| 634 821 | 
             
                  SugarJar::Log.debug("remotes is #{remotes}")
         | 
| @@ -646,6 +833,13 @@ class SugarJar | |
| 646 833 | 
             
                  @remote
         | 
| 647 834 | 
             
                end
         | 
| 648 835 |  | 
| 836 | 
            +
                def branch_from_ref(ref, type = :local)
         | 
| 837 | 
            +
                  # local branches are refs/head/XXXX
         | 
| 838 | 
            +
                  # remote branches are refs/remotes/<remote>/XXXX
         | 
| 839 | 
            +
                  base = type == :local ? 2 : 3
         | 
| 840 | 
            +
                  ref.split('/')[base..].join('/')
         | 
| 841 | 
            +
                end
         | 
| 842 | 
            +
             | 
| 649 843 | 
             
                def color(string, *colors)
         | 
| 650 844 | 
             
                  if @color
         | 
| 651 845 | 
             
                    pastel.decorate(string, *colors)
         | 
| @@ -660,5 +854,44 @@ class SugarJar | |
| 660 854 | 
             
                    Pastel.new
         | 
| 661 855 | 
             
                  end
         | 
| 662 856 | 
             
                end
         | 
| 857 | 
            +
             | 
| 858 | 
            +
                def determine_cli(cli)
         | 
| 859 | 
            +
                  return cli if %w{gh hub}.include?(cli)
         | 
| 860 | 
            +
             | 
| 861 | 
            +
                  die("'github_cli' has unknown setting: #{cli}") unless cli == 'auto'
         | 
| 862 | 
            +
             | 
| 863 | 
            +
                  SugarJar::Log.debug('github_cli set to auto')
         | 
| 864 | 
            +
             | 
| 865 | 
            +
                  if which_nofail('gh')
         | 
| 866 | 
            +
                    SugarJar::Log.debug('Found "gh"')
         | 
| 867 | 
            +
                    return 'gh'
         | 
| 868 | 
            +
                  end
         | 
| 869 | 
            +
                  if which_nofail('hub')
         | 
| 870 | 
            +
                    SugarJar::Log.debug('Did not find "gh" but did find "hub"')
         | 
| 871 | 
            +
                    return 'hub'
         | 
| 872 | 
            +
                  end
         | 
| 873 | 
            +
             | 
| 874 | 
            +
                  die(
         | 
| 875 | 
            +
                    'Neither "gh" nor "hub" found in PATH, please ensure at least one ' +
         | 
| 876 | 
            +
                    'of these utilities is in the PATH. If both are available you can ' +
         | 
| 877 | 
            +
                    'specify which to use with --github-cli',
         | 
| 878 | 
            +
                  )
         | 
| 879 | 
            +
                end
         | 
| 880 | 
            +
             | 
| 881 | 
            +
                def hub?
         | 
| 882 | 
            +
                  @cli == 'hub'
         | 
| 883 | 
            +
                end
         | 
| 884 | 
            +
             | 
| 885 | 
            +
                def gh?
         | 
| 886 | 
            +
                  @cli == 'gh'
         | 
| 887 | 
            +
                end
         | 
| 888 | 
            +
             | 
| 889 | 
            +
                def ghcli_nofail(*args)
         | 
| 890 | 
            +
                  gh? ? gh_nofail(*args) : hub_nofail(*args)
         | 
| 891 | 
            +
                end
         | 
| 892 | 
            +
             | 
| 893 | 
            +
                def ghcli(*args)
         | 
| 894 | 
            +
                  gh? ? gh(*args) : hub(*args)
         | 
| 895 | 
            +
                end
         | 
| 663 896 | 
             
              end
         | 
| 664 897 | 
             
            end
         | 
    
        data/lib/sugarjar/config.rb
    CHANGED
    
    | @@ -6,14 +6,15 @@ class SugarJar | |
| 6 6 | 
             
              # This is stuff like log level, github-user, etc.
         | 
| 7 7 | 
             
              class Config
         | 
| 8 8 | 
             
                DEFAULTS = {
         | 
| 9 | 
            -
                  ' | 
| 9 | 
            +
                  'github_cli' => 'auto',
         | 
| 10 | 
            +
                  'github_user' => ENV.fetch('USER'),
         | 
| 10 11 | 
             
                  'fallthru' => true,
         | 
| 11 12 | 
             
                }.freeze
         | 
| 12 13 |  | 
| 13 14 | 
             
                def self._find_ordered_files
         | 
| 14 15 | 
             
                  [
         | 
| 15 16 | 
             
                    '/etc/sugarjar/config.yaml',
         | 
| 16 | 
            -
                    "#{ | 
| 17 | 
            +
                    "#{Dir.home}/.config/sugarjar/config.yaml",
         | 
| 17 18 | 
             
                  ].select { |f| File.exist?(f) }
         | 
| 18 19 | 
             
                end
         | 
| 19 20 |  | 
    
        data/lib/sugarjar/util.rb
    CHANGED
    
    | @@ -26,11 +26,25 @@ class SugarJar | |
| 26 26 | 
             
                  exit(1)
         | 
| 27 27 | 
             
                end
         | 
| 28 28 |  | 
| 29 | 
            -
                def  | 
| 29 | 
            +
                def git_nofail(*args)
         | 
| 30 30 | 
             
                  if %w{diff log grep branch}.include?(args[0]) &&
         | 
| 31 31 | 
             
                     args.none? { |x| x.include?('color') }
         | 
| 32 32 | 
             
                    args << (@color ? '--color' : '--no-color')
         | 
| 33 33 | 
             
                  end
         | 
| 34 | 
            +
                  SugarJar::Log.trace("Running: git #{args.join(' ')}")
         | 
| 35 | 
            +
                  Mixlib::ShellOut.new([which('git')] + args).run_command
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def git(*args)
         | 
| 39 | 
            +
                  s = git_nofail(*args)
         | 
| 40 | 
            +
                  s.error!
         | 
| 41 | 
            +
                  s
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def hub_nofail(*args)
         | 
| 45 | 
            +
                  # this allows us to use 'hub' stuff that's top-level, but is under
         | 
| 46 | 
            +
                  # repo for this.
         | 
| 47 | 
            +
                  args.delete_at(0) if args[0] == 'repo'
         | 
| 34 48 | 
             
                  SugarJar::Log.trace("Running: hub #{args.join(' ')}")
         | 
| 35 49 | 
             
                  s = Mixlib::ShellOut.new([which('hub')] + args).run_command
         | 
| 36 50 | 
             
                  if s.error?
         | 
| @@ -84,13 +98,43 @@ class SugarJar | |
| 84 98 | 
             
                  s
         | 
| 85 99 | 
             
                end
         | 
| 86 100 |  | 
| 101 | 
            +
                def gh_nofail(*args)
         | 
| 102 | 
            +
                  SugarJar::Log.trace("Running: gh #{args.join(' ')}")
         | 
| 103 | 
            +
                  s = Mixlib::ShellOut.new([which('gh')] + args).run_command
         | 
| 104 | 
            +
                  if s.error? && s.stderr.include?('gh auth')
         | 
| 105 | 
            +
                    SugarJar::Log.info(
         | 
| 106 | 
            +
                      'gh was run but no github token exists. Will run "gh auth login" ' +
         | 
| 107 | 
            +
                      "to force\ngh to authenticate...",
         | 
| 108 | 
            +
                    )
         | 
| 109 | 
            +
                    unless system(which('gh'), 'auth', 'login', '-p', 'ssh')
         | 
| 110 | 
            +
                      SugarJar::Log.fatal(
         | 
| 111 | 
            +
                        'That failed, I will bail out. Hub needs to get a github ' +
         | 
| 112 | 
            +
                        'token. Try running "gh auth login" (will list info about ' +
         | 
| 113 | 
            +
                        'your account) and try this again when that works.',
         | 
| 114 | 
            +
                      )
         | 
| 115 | 
            +
                      exit(1)
         | 
| 116 | 
            +
                    end
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
                  s
         | 
| 119 | 
            +
                end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                def gh(*args)
         | 
| 122 | 
            +
                  s = gh_nofail(*args)
         | 
| 123 | 
            +
                  s.error!
         | 
| 124 | 
            +
                  s
         | 
| 125 | 
            +
                end
         | 
| 126 | 
            +
             | 
| 87 127 | 
             
                def in_repo
         | 
| 88 | 
            -
                  s =  | 
| 128 | 
            +
                  s = git_nofail('rev-parse', '--is-inside-work-tree')
         | 
| 89 129 | 
             
                  !s.error? && s.stdout.strip == 'true'
         | 
| 90 130 | 
             
                end
         | 
| 91 131 |  | 
| 92 132 | 
             
                def repo_root
         | 
| 93 | 
            -
                   | 
| 133 | 
            +
                  git('rev-parse', '--show-toplevel').stdout.strip
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                def repo_name
         | 
| 137 | 
            +
                  repo_root.split('/').last
         | 
| 94 138 | 
             
                end
         | 
| 95 139 | 
             
              end
         | 
| 96 140 | 
             
            end
         | 
    
        data/lib/sugarjar/version.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: sugarjar
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0 | 
| 4 | 
            +
              version: 1.0.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Phil Dibowitz
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date:  | 
| 11 | 
            +
            date: 2023-10-23 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: mixlib-log
         | 
| @@ -95,7 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 95 95 | 
             
                - !ruby/object:Gem::Version
         | 
| 96 96 | 
             
                  version: '0'
         | 
| 97 97 | 
             
            requirements: []
         | 
| 98 | 
            -
            rubygems_version: 3.3. | 
| 98 | 
            +
            rubygems_version: 3.3.7
         | 
| 99 99 | 
             
            signing_key:
         | 
| 100 100 | 
             
            specification_version: 4
         | 
| 101 101 | 
             
            summary: A git/github helper script
         |