{"id":15632039,"url":"https://github.com/jaymzh/sugarjar","last_synced_at":"2025-04-07T11:09:54.254Z","repository":{"id":44808551,"uuid":"268215187","full_name":"jaymzh/sugarjar","owner":"jaymzh","description":"A helper utility for a better git/github experience.","archived":false,"fork":false,"pushed_at":"2025-03-31T01:27:18.000Z","size":323,"stargazers_count":23,"open_issues_count":0,"forks_count":8,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-01T18:59:51.419Z","etag":null,"topics":["git","github"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jaymzh.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-05-31T05:27:55.000Z","updated_at":"2025-03-18T17:02:03.000Z","dependencies_parsed_at":"2023-12-19T02:23:58.410Z","dependency_job_id":"0d7430bb-0b1c-4c87-a1c4-89323adbd536","html_url":"https://github.com/jaymzh/sugarjar","commit_stats":{"total_commits":153,"total_committers":8,"mean_commits":19.125,"dds":0.05228758169934644,"last_synced_commit":"8a04f722e6c616b5217228e3046d93c5f91ff4e2"},"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaymzh%2Fsugarjar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaymzh%2Fsugarjar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaymzh%2Fsugarjar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaymzh%2Fsugarjar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jaymzh","download_url":"https://codeload.github.com/jaymzh/sugarjar/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247640465,"owners_count":20971557,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["git","github"],"created_at":"2024-10-03T10:42:27.227Z","updated_at":"2025-04-07T11:09:54.230Z","avatar_url":"https://github.com/jaymzh.png","language":"Ruby","readme":"# SugarJar\n\n[![Lint](https://github.com/jaymzh/sugarjar/workflows/Lint/badge.svg)](https://github.com/jaymzh/sugarjar/actions?query=workflow%3ALint)\n[![Unittest](https://github.com/jaymzh/sugarjar/workflows/Unittests/badge.svg)](https://github.com/jaymzh/sugarjar/actions?query=workflow%3AUnittests)\n[![DCO](https://github.com/jaymzh/sugarjar/workflows/DCO%20Check/badge.svg)](https://github.com/jaymzh/sugarjar/actions?query=workflow%3A%22DCO+Check%22)\n\n\u003e [!IMPORTANT]\n\u003e\n\u003e Future versions of SugarJar will drop `hub` support. If you haven't already,\n\u003e please start planning to move to `gh`.\n\nWelcome to SugarJar - a git/github helper. It needs one of the GitHub CLI's:\neither [gh](https://cli.github.com/) or the older [hub](https://hub.github.com/).\n\nSugarJar is inspired by [arcanist](https://github.com/phacility/arcanist), and\nits replacement at Facebook, JellyFish. Many of the features they provide for\nthe Phabricator workflow this aims to bring to the GitHub workflow.\n\nIn particular there are a lot of helpers for using a squash-merge workflow that\nis poorly handled by the standard toolsets.\n\nIf you miss Mondrian or Phabricator - this is the tool for you!\n\nIf you don't, there's a ton of useful stuff for everyone!\n\n## Installation\n\nSugarjar is packaged in a variety of Linux distributions - see if it's on the\nlist here, and if so, use your package manager (or `gem`) to install it:\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/sugarjar.svg?exclude_unsupported=1)](https://repology.org/project/sugarjar/versions)\n\nIf you are using a Linux distribution version that is end-of-life'd, click the\nabove image, it'll take you to a page that lists unsupported distro versions\nas well (they'll have older SugarJar, but they'll probably still have some\nversion).\n\nUbuntu users, Ubuntu versions prior to 24.x cannot be updated, so if you're on\nan older Ubuntu please use [this\nPPA](https://launchpad.net/~michel-slm/+archive/ubuntu/sugarjar) from our\nUbuntu package maintainer.\n\nFor MacOS users, we recommend using Homebrew - SugarJar is now in Homebrew Core.\n\nFinally, if none of those work for you, you can clone this repo and run it\ndirectly from there.\n\n## Auto cleanup squash-merged branches\n\nIt is common for a PR to go back and forth with a variety of nits, lint fixes,\ntypos, etc. that can muddy history. So many projects will \"squash and merge\"\nwhen they accept a pull request. However, that means `git branch -d \u003cbranch\u003e`\ndoesn't work. Git will tell you the branch isn't fully merged. You can, of\ncourse `git branch -D \u003cbranch\u003e`, but that does no safety checks at all, it\nforces the deletion.\n\nEnter `sj bclean` - it determines if the contents of your branch has been merge\nand safely deletes if so.\n\n``` shell\nsj bclean\n```\n\nWill delete a branch, if it has been merged, **even if it was squash-merged**.\n\nYou can pass it a branch if you'd like (it defaults to the branch you're on):\n`sj bclean \u003cbranch\u003e`.\n\nBut it gets better! You can use `sj bcleanall` to remove all branches that have\nbeen merged:\n\n```shell\n$ git branch\n* argparse\n  master\n  feature\n  hubhost\n$ git bcleanall\nSkipping branch argparse - there are unmerged commits\nReaped branch feature\nReaped branch hubhost\n```\n\n## Smarter clones and remotes\n\nThere's a pattern to every new repo we want to contribute to. First we fork,\nthen we clone the fork, then we add a remote of the upstream repo. It's\nmonotonous. SugarJar does this for you:\n\n```shell\nsj smartclone jaymzh/sugarjar\n```\n\n(also `sj sclone`)\n\nThis will:\n\n* Make a fork of the repo, if you don't already have one\n* Clone your fork\n* Add the original as an 'upstream' remote\n\nNote that it takes short names for repos. No need to specify a full URL,\njust a $org/$repo.\n\nLike `git clone`, `sj sclone` will accept an additional argument as the\ndestination directory to clone to. It will also pass any other unknown options\nto `git clone` under the hood.\n\n## Work with stacked branches more easily\n\nIt's important to break changes into reviewable chunks, but working with\nstacked branches can be confusing. SugarJar provides several tools to make this\neasier.\n\nFirst, and foremost, is `feature` and `subfeature`. Regardless of stacking, the\nway to create a new feature bracnh with sugarjar is with `sj feature` (or `sj\nf` for short):\n\n```shell\n$ sj feature mynewthing\nCreated feature branch mynewthing based on origin/main\n```\n\nA \"feature\" in SugarJar parliance just means that the branch is always created\nfrom \"most_main\" - this is usually \"upstream/main\", but SJ will figure out\nwhich remote is the \"upstream\", even if it's \"origin\", and then will determine\nthe primary branch (\"main\" or for older repos \"master\"). It's also smart enough\nto fetch that remote first to make sure you're working on the latest HEAD.\n\nWhen you want to create a stacked PR, you can create \"subfeature\", which, at\nits core is just a branch created from the current branch:\n\n```shell\n$ sj subfeature dependentnewthing\nCreated feature branch dependentnewthing based on mynewthing\n```\n\nIf you create branches like this then sugarjar can now make several things\nmuch easier:\n\n* `sj up` will rebase intelligently\n* After an `sj bclean` of a branch earlier in the tree, `sj up` will update\n  the tracked branch to \"most_main\"\n\nThere are two commands that will show you the state of your stacked branches:\n\n* `sj binfo` - shows the current branch and its ancestors up to your primary branch\n* `sj smartlist` (aka `sj sl`) - shows you the whole tree.\n\nTo continue with the example above, my `smartlist` might look like:\n\n```text\n$ sj sl\n* 59c0522 (HEAD -\u003e dependentnewthing) anothertest\n* 6ebaa28 (mynewthing) test\no 7a0ffd0 (tag: v1.1.2, origin/main, origin/HEAD, main) Version bump (#160)\n```\n\nThis is simple. Now lets make a different feature stack:\n\n```text\n$ sj feature anotherfeature\nCreated feature branch anotherfeature based on origin/main\n# do stuff\n$ sj subfeature dependent2\nCreated feature branch dependent2 based on anotherfeature\n# do stuff\n```\n\nThe `smartlist` will now show us this tree, and it's a bit more interesting:\n\n```text\n$ sj sl\n* af6f143 (HEAD -\u003e dependent2) morestuff\n* 028c7f4 (anotherfeature) stuff\n| * 59c0522 (dependentnewthing) anothertest\n| * 6ebaa28 (mynewthing) test\n|/\no 7a0ffd0 (tag: v1.1.2, origin/main, origin/HEAD, main) Version bump (#160)\n```\n\nNow, what happens if I make a change to `mynewthing`?\n\n```text\n$ sj co mynewthing\nSwitched to branch 'mynewthing'\nYour branch is ahead of 'origin/main' by 1 commit.\n  (use \"git push\" to publish your local commits)\n$ echo 'randomchange' \u003e\u003e README.md\n$ git commit -a -m change\n[mynewthing d33e082] change\n 1 file changed, 1 insertion(+)\n$ sj sl\n* d33e082 (HEAD -\u003e mynewthing) change\n| * af6f143 (dependent2) morestuff\n| * 028c7f4 (anotherfeature) stuff\n| | * 59c0522 (dependentnewthing) anothertest\n| |/\n|/|\n* | 6ebaa28 test\n|/\no 7a0ffd0 (tag: v1.1.2, origin/main, origin/HEAD, main) Version bump (#160)\n```\n\nWe can see here now that `dependentnewthing`, is based off a commit that _used_\nto be `mynewthing`, but `mynewthing` has moved. But SugarJar will handle this\nall correctly when we ask it to update the branch:\n\n```text\n$ sj co dependentnewthing\nSwitched to branch 'dependentnewthing'\nYour branch and 'mynewthing' have diverged,\nand have 1 and 1 different commits each, respectively.\n  (use \"git pull\" if you want to integrate the remote branch with yours)\n$ sj up\ndependentnewthing rebased on mynewthing\n$ sj sl\n* 93ed585 (HEAD -\u003e dependentnewthing) anothertest\n* d33e082 (mynewthing) change\n* 6ebaa28 test\n| * af6f143 (dependent2) morestuff\n| * 028c7f4 (anotherfeature) stuff\n|/\no 7a0ffd0 (tag: v1.1.2, origin/main, origin/HEAD, main) Version bump (#160)\n```\n\nNow, lets say that `mynewthing` gets merged and we use `bclean` to clean it all\nup, what happens then?\n\n```text\n$ sj up\nThe brach we were tracking is gone, resetting tracking to origin/main\ndependentnewthing rebased on origin/main\n```\n\n### Creating Stacked PRs with subfeatures\n\nWhen dependent branches are created with `subfeature`, when you create a PR,\nSugarJar will automatically set the 'base' of the PR to the parent branch. By\ndefault it'll prompt you about this, but you can set `pr_autostack` to `true`\nin your config to tell it to always do this (or `false` to never do this):\n\n```text\n$ sj spr\nAutofilling in PR from commit message\nIt looks like this is a subfeature, would you like to base this PR on mynewthing? [y/n] y\n...\n```\n\n## Have a better lint/unittest experience!\n\nEver made a PR, only to find out later that it failed tests because of some\nsmall lint issue? Not anymore! SJ can be configured to run things before\npushing. For example,in the SugarJar repo, we have it run Rubocop (ruby lint)\nand Markdownlint \"on_push\". If those fail, it lets you know and doesn't push.\n\nYou can configure SugarJar to tell it how to run both lints and unittests for\na given repo and if one or both should be run prior to pushing.\n\nThe details on the config file format is below, but we provide three commands:\n\n```shell\ngit lint\n```\n\nRun all linters.\n\n```shell\ngit unit\n```\n\nRun all unittests.\n\n```shell\ngit smartpush # or spush\n```\n\nRun configured push-time actions (nothing, lint, unit, both), and do not\npush if any of them fail.\n\n## Better push defaults\n\nIn addition to running pre-push tests for you `smartpush` also picks smart\ndefaults for push. So if you `sj spush` with no arguments, it uses the\n`origin` remote and the same branch name you're on as the remote branch.\n\n## Cleaning up your own history\n\nPerhaps you contribute to a project that prefers to use merge commits, so you\nlike to clean up your own history. This is often difficult to get right - a\ncombination of rebases, amends and force pushes. We provide two commands here\nto help.\n\nThe first is pretty straight forward and is basically just an alias: `sj\namend`. It will amend whatever you want to the most recent commit (just an\nalias for `git commit --amend`). It has a partner `qamend` (or `amendq` if you\nprefer) that will do so without prompting to update your commit message.\n\nSo now you've rebased or amended, pushing becomes challenging. You can `git push\n--force`, but everyone knows that's incredibly dangerous. Is there a better\nway? There is! Git provides `git push --force-with-lease` - it checks to make\nsure you're up-to-date with the remote before forcing the push. But man that\ncommand is a mouthful! Enter `sj fpush`. It has all the smarts of `sj\nsmartpush` (runs configured pre-push actions), but adds `--force-with-lease` to\nthe command!\n\n## Better feature branches\n\nWhen you want to start a new feature, you want to start developing against\nlatest. That's why `sj feature` defaults to creating a branch against what we\ncall \"most master\". That is, `upstream/master` if it exists, otherwise\n`origin/master` if that exists, otherwise `master`. You can pass in an\nadditional argument to base it off of something else.\n\n```shell\n$ git branch\n  master\n  test1\n  test2\n* test2.1\n  test3\n$ sj feature test-branch\nCreated feature branch test-branch based on origin/master\n$ sj feature dependent-feature test-branch\nCreated feature branch dependent-feature based on test-branch\n```\n\nAdditionally you can specify a `feature_prefix` in your config which will cause\n`feature` to create branches prefixed with your `feature_prefix` and will also\ncause `co` to checkout branches with that prefix. This is useful when organizations\nuse branch-based workflows and branches need to be prefixed with e.g. `$USER/`.\n\nFor example, if your prefix was `user/`, then `sj feature foo` would create\n`user/foo`, and `sj co foo` would switch to `user/foo`.\n\n## Smartlog\n\nSmartlog will show you a tree diagram of your branches! Simply run `sj\nsmartlog` or `sj sl` for short.\n\n![smartlog screenshot](https://github.com/jaymzh/sugarjar/blob/main/smartlog.png)\n\n## Pulling in suggestions from the web\n\nWhen someone 'suggests' a change in the GitHub WebUI, once you choose to commit\nthem, your origin and local branches are no longer in-sync. The\n`pullsuggestions` command will attempt to merge in any remote commits to your\nlocal branch. This command will show a diff and ask for confirmation before\nattempting the merge and  - if allowed to continue - will use a fast-forward\nmerge.\n\n## And more!\n\nSee `sj help` for more commands!\n\n## Using SugarJar as a git wrapper\n\nSugarJar, by default, will pass any command it doesn't know straight to `hub`\n(which passes commands **it** doesn't know to `git`). If you have configured\nSugarJar to use `gh` instead of `hub`, then it will pass commands straight to\n`git` since `gh` doesn't act as a `git` wrapper.\n\nAs such you can alias it to `git` and just have a super-git.\n\n```shell\n$ alias git=sj\n$ git config -l | grep color\ncolor.diff=auto\ncolor.status=auto\ncolor.branch=auto\ncolor.branch.current=yellow reverse\ncolor.branch.local=yellow\ncolor.branch.remote=green\n$ git br\n* dependent-feature 44cf9e2 Lint/gemspec cleanups\n  master            44cf9e2 Lint/gemspec cleanups\n  test-branch       44cf9e2 Lint/gemspec cleanups\n  test1             c808eae [ahead 1] test1\n  test2             e545b41 test2\n  test2.1           c1831b3 test2.1\n  test3             e451865 test3\n```\n\nIt's for this reason that SugarJar doesn't have conflicting command names. You\ncan turn off fallthru by setting `fallthru: false` in your config.\n\nThe only command we \"override\" is `version`, in which case we not only print\nour version, but also call `hub version` which prints its version and calls\n`git version` too!\n\n## Configuration\n\nSugarjar will read in both a system-level config file\n(`/etc/sugarjar/config.yaml`) and a user-level config file\n`~/.config/sugarjar/config.yaml`, if they exist. Anything in the user config\nwill override the system config, and command-line options override both. The\nyaml file is a straight key-value pair of options without their '--'. For\nexample:\n\n```yaml\nlog_level: debug\ngithub_user: jaymzh\n```\n\nIn addition, the environment variable `SUGARJAR_LOGLEVEL` can be defined to set\na log level. This is primarily used as a way to turn debug on earlier in order to\ntroubleshoot configuration parsing.\n\n## Repository Configuration\n\nSugarjar looks for a `.sugarjar.yaml` in the root of the repository to tell it\nhow to handle repo-specific things. Currently there options are:\n\n* `lint` - A list of scripts to run on `sj lint`. These should be linters like\n  rubocop or pyflake. Linters will be run from the root of the repo.\n* `lint_list_cmd` - A command to run which will print out linters to run, one\n  per line. Takes precedence over `lint`. The command (and the resulting\n  linters) will be run from the root of the repo.\n* `unit` - A list of scripts to run on `sj unit`. These should be unittest\n  runners like rspec or pyunit. Test will be run from the root of the repo.\n* `unit_list_cmd` - A command to run which will print out the unit tests to\n  run, one more line. Takes precedence over `unit`. The command (and the\n  resulting unit tests) will be run from the root of the repo.\n* `on_push` - A list of types (`lint`, `unit`) of checks to run before pushing.\n  It is highly recommended this is only `lint`. The goal here is to allow for\n  the user to get quick stylistic feedback before pushing their branch to avoid\n  the push-fix-push-fix loop.\n* `commit_template` - A path to a commit template to set in the `commit.template`\n  git config for this repo. Should be either a fully-qualified path, or a path\n  relative to the repo root.\n* `include_from` - This will read an additional repoconfig file and merge it\n  into the one being read. The value should be relative to the root of the\n  repo. This will not error if the file does not exist, it is intended for\n  organizations to allow users to optionally extend a default repo config.\n* `overwrite_from` - Same as `include_from`, but completely overwrites the\n  base configuration if the file is found.\n\nExample configuration:\n\n```yaml\nlint:\n  - scripts/lint\nunit:\n  - scripts/unit\non_push:\n  - lint\ncommit_template: .commit-template.txt\n```\n\n### Commit Templates\n\nWhile GitHub provides a way to specify a pull-request template by putting the\nright file into a repo, there is no way to tell git to automatically pick up a\ncommit template by dropping a file in the repo. Users must do something like:\n`git config commit.template \u003cfile\u003e`. Making each developer do this is error\nprone, so this setting will automatically set this up for each developer.\n\n## Enterprise GitHub\n\nLike `hub`, SugarJar supports GitHub Enterprise. In fact, we provide extra\nfeatures just for it.\n\nWe recommend the global or user config specify the `github_host`. However, most\nusers will also have a few repos from upstream so always specifying a\n`github_host` is sub-optimal.\n\nSo, when you overwrite the `github_host` on the command line, we go ahead and\nset the `hub.host` git config in that single repo so that it'll \"just work\"\nfrom there on out.\n\nIn other words, assuming your global SJ config has `github_host:\ngithub.sample.com`, and the you clone sugarjar with:\n\n```shell\nsj clone jaymzh/sugarjar --github-host githuh.com\n```\n\nWe will add the `hub.host` to the `sugarjar` clone so that future `hub` or `sj`\ncommands work without needing to specify..\n\n## Choosing a GitHub CLI\n\nSugarJar will use `gh` if it is available or otherwise fall back to `hub`. You\ncan override this by specifying `--github-cli` on the command line or setting\n`github_cli` to either `gh` or `hub` (it defaults to `auto`) in your\nconfiguration.\n\n## FAQ\n\n**Why the name SugarJar?**\n\nIt's mostly a backronym. Like jellyfish, I wanted two letters that were on home\nrow on different sides of the keyboard to make it easy to type. I looked at the\npossible options that where there and not taken and tried to find one I could\nmake an appropriate name out of. Since this utility adds lots of sugar to git\nand github, it seemed appropriate.\n\n**Why did you originally use `hub` instead of the newer `gh` CLI?**\n\nWhen I originally wrote SugarJar, `gh` was in early development, and `hub` had\nmany more features. In addition, I originally wrote SugarJar to be a wrapper\nfor git/hub, and `hub` allows this but `gh` does not.\n\nWhen `gh` matured, we added experimental `gh` support in 0.0.11, and switched the\ndefault to prefer `gh` in 1.0.0. We will drop `hub` support in 2.0.\n\n**I'd like to package SugarJar for my favorite distro/OS, is that OK?**\n\nOf course! But I'd appreciate you emailing me to give me a heads up. Doing so\nwill allow me to make sure it shows up in the Repology badge above.\n\n**What platforms does it work on?**\n\nSince it's Ruby, it should work across all platforms, however, it's developed\nand primarily tested on Linux as well as regularly used on Mac. I've not tested\nit on Windows, but I'll happily accept patches for Windows compatibility.\n\n**How do I get tab-completion?**\n\nIf the package for your OS/distro didn't set it up manually, you should find\nthat `sugarjar_completion.bash` is included in the package, and you can simply\nsource that in your dotfiles, assuming you are using bash.\n\n**What happens now that Sapling is released?**\n\nSugarJar isn't going anywhere anytime soon. This was meant to replace arc/jf,\nwhich has now been open-sourced as [Sapling](https://sapling-scm.com/), so I\nhighly recommend taking a look at that!\n\nSapling is a great tool and solves a variety of problems SugarJar will never be\nable to. However, it is a significant workflow change, that won't be\nappropriate for all users or use-cases. Similarly there are workflows and tools\nthat Sapling breaks. So worry not, SugarJar will continue to be maintained and\ndeveloped\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaymzh%2Fsugarjar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjaymzh%2Fsugarjar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaymzh%2Fsugarjar/lists"}