git-shit
v1.9.0
Published
git-flow feature workflow that ends in a PR into staging: publish the branch, then create and merge the PR from the terminal with the GitHub CLI (gh) — or fall back to opening the Bitbucket/GitHub PR page pre-filled and auto-clicking Create pull request.
Maintainers
Readme
git-shit
git-flow feature workflow that ends in a pull request into staging — publish the branch, then create (and merge) the PR straight from the terminal with the GitHub CLI. On Bitbucket, or without gh, it falls back to opening the pre-filled PR page in Chrome and auto-clicking Create pull request.
git flow feature finish is deliberately not used: it merges locally and pushes directly. With git-shit, the merge into staging happens through a PR.
Install
npm install -g git-shitBecause the binary is named git-shit, git also picks it up as a subcommand: git shit ship works too.
For the terminal PR flow on GitHub remotes, also install the GitHub CLI and log in once:
brew install gh # or see https://cli.github.com
gh auth loginUsage
git-shit start my-fix # runs `git flow feature start my-fix`
git-shit start my-fix production # same, but branch off origin/production
git-shit start part-2 --on=my-fix # stack part-2 on my-fix (its PR targets my-fix)
# ...do your work, commit as usual...
git-shit status # where am I? published? PR state? ahead/behind the base?
git-shit sync # catch the branch up to its base (rebase origin/staging in)
git-shit sync --merge # same, but merge the base in instead of rebasing
git-shit ship # pushes the branch and opens a PR into the base (default: staging)
git-shit ship develop # same, but the PR targets `develop` instead
git-shit ship --draft # create the PR as a draft (GitHub + gh only)
git-shit merge # merge the open PR from the terminal, then clean up
git-shit merge --squash # same, squash-merged (also: --rebase)
git-shit merge --when-green # wait for checks to pass, then merge + notify
git-shit done # cleanup only: checkout the base, pull, delete branch, prune
git-shit list # interactive board of every feature/* branch (--plain for a static table)
git-shit completion zsh # print a shell-completion script (also: bash, fish)
git-shit help # show usage (also --help, -h)
git-shit version # show version (also --version, -v)The default PR target is staging, but it's configurable — see Configuration.
start <name> [base] [--on=<parent>]
Runs git flow feature start. With base, the feature branches off origin/<base> (freshly fetched) instead of git-flow's default develop — e.g. git-shit start my-fix production for a fix that belongs on production. The base is remembered on the branch, so ship, merge-cleanup, done, and status all use it as this branch's default PR target instead of staging (an explicit argument still wins, e.g. git-shit ship develop).
With --on=<parent> it stacks the new branch on another feature branch instead of a long-lived base — see Stacked PRs.
Stacked PRs
Break a big change into a chain of small, reviewable PRs where each builds on the last — without waiting for the first to merge. gh has no notion of this; git-shit records the parent as the child's base and keeps the stack honest for you.
git-shit start api # feature/api -> staging
# ...commit, then...
git-shit ship # PR: feature/api -> staging
git-shit start ui --on=api # feature/ui -> feature/api (stacked)
# ...commit, then...
git-shit ship # PR: feature/ui -> feature/apistart <name> --on=<parent>branches off the local parent's tip and records the parent as this branch's PR target.<parent>can be the short name (api) or the full branch (feature/api).shipon a stacked branch opens the PR against its parent, and the PR body lists only the commits this branch adds on top of the parent. The parent must be shipped (onorigin) first — if it isn't,shiptells you to ship it before the child.- When the parent merges — via
git-shit merge, or a browser merge followed bygit-shit done— each direct child is automatically restacked onto the parent's base: rebased withgit rebase --onto(dropping the parent's now-merged commits), its recorded base and open-PR target retargeted, and force-pushed. If a rebase hits conflicts, that child is left untouched and the exact manual command is printed. Deeper descendants keep their own parent; catch them up withgit-shit synconce their parent is restacked.
status and list both show the stack — status labels the base as a stacked parent, and list indents each child under its parent.
ship [dest] [--draft] [--web] [--reviewer=…] [--label=…] [--assignee=…]
- Verifies you have no uncommitted changes and that the destination branch (default: the base recorded by
start, elsestaging) exists onorigin. Afeature/*branch is the normal case, but any branch can ship — off afeature/*branch it just prints a note and carries on (it only refuses to ship a branch into itself). - Publishes the branch —
git flow feature publishfor afeature/*branch in a git-flow-initialised repo, otherwise a plaingit push -u origin <branch>— or just pushes if it's already onorigin. Soshipworks even in a repo where you never rangit flow init(afeature/*branch there is just published with a plain push). - Creates the PR:
- GitHub remote +
ghlogged in — creates the PR from the terminal withgh pr create. The title and body come from the branch's commits: a single-commit branch uses that commit's subject and full message body, while a multi-commit branch uses the first commit's subject as the title and a bullet list of every commit subject as the body. If the repo has a pull-request template, it's used as the body instead. If the branch already has an open PR, it just tells you (the push already updated it).--draftopens it as a draft;--webskipsghand forces the browser flow. - Bitbucket, or no
gh— opens the "new pull request" page in Chrome, pre-filled with source, destination, and title. On macOS it polls the active Chrome tab and auto-clicks Create pull request once it renders. Workspace/repo are auto-detected fromorigin(SSH or HTTPS).
- GitHub remote +
Reviewers, labels, and assignees (GitHub + gh, on PR creation): pass --reviewer=alice,bob, --label=feature,needs-qa, or --assignee=@me (comma-separated, use the = form). Each is unioned with a per-repo default from git config — set gitshit.reviewers, gitshit.labels, and gitshit.assignees once and every PR gets them for free (see Configuration). They only apply when a new PR is created, and are ignored (with a note) in the Bitbucket/browser flow.
PR title and body
When creating a PR with gh, git-shit fills the title and body from the commits your branch adds on top of the base:
- One commit — the title is its subject and the body is its full message body (the common case if you keep one commit per branch).
- Several commits — the title is the first commit's subject and the body is a bullet list of every commit subject, oldest first — a ready-made summary rather than just the tip commit.
- Pull-request template — if the repo has one (
.github/pull_request_template.md,PULL_REQUEST_TEMPLATE.md,docs/…, and the usual variants), its contents become the body so your team's checklist/format is preserved; the title still comes from the commits.
Either way it's just the starting point — edit the PR on GitHub afterwards if you want. (The Bitbucket/browser fallback only pre-fills the title.)
sync [dest] [--merge]
Brings the latest base into the current branch so it doesn't drift behind while you work (status tells you how far behind; sync is how you catch up). It:
- Refuses if you have uncommitted changes — a rebase/merge needs a clean tree.
- Fetches and prunes
origin, then checks the base (default: the base recorded bystart, elsestaging) exists there. - If the base has no new commits, says so and stops without touching your tree.
- Otherwise rebases your branch onto
origin/<base>— or merges the base in with--merge. On conflicts it leaves the in-progress rebase/merge in place and prints exactly how to continue (git rebase --continue) or back out (git rebase --abort).
If the branch is already published, sync reminds you to update the open PR: a rebase rewrote history, so it needs git push --force-with-lease origin <branch>; a merge only adds a commit, so a plain git-shit ship is enough.
merge [--merge|--squash|--rebase] [--when-green] (GitHub + gh)
Merges the current branch's open PR with gh pr merge (default: a merge commit), then runs the done cleanup against the PR's actual base branch. Works on a feature/* branch or any other branch you shipped (off a feature/* branch it prints a note and the cleanup leaves the local branch in place). Refuses if you have unpushed commits, if there's no open PR, or if the PR is still a draft. On Bitbucket, merge in the browser and run git-shit done instead.
With --when-green it doesn't merge right away — it polls the PR's checks every 20s and merges only once they're all passing, then cleans up. A failing check stops it (no merge); a PR with no checks merges immediately. Either way you get a desktop notification (plus a terminal bell) when it merges or a check fails, so you can kick it off and walk away. gh pr merge --auto queues a merge on GitHub's side but doesn't do the local done cleanup or notify you — this closes that loop. (Stop the wait any time with Ctrl-C; it gives up after an hour.)
done [dest]
Run after the PR is merged in the browser (merge does this for you). Checks out the destination branch (default: the base recorded by start, else staging), pulls, deletes the local feature branch, and prunes stale remote-tracking refs. Warns if the branch doesn't appear merged (normal for squash merges).
status
Shows the current branch, its recorded base (if not staging), whether it's clean, whether it's published to origin, unpushed commits, and ahead/behind counts vs the base. With gh on a GitHub remote it also shows the live PR state — number, open/draft/merged, review decision, mergeability, and URL.
list [--plain]
A dashboard of all your in-flight work — every local feature/* branch at once, most-recently-worked first, instead of one branch at a time. For each it shows the base, publish state, and (with gh on a GitHub remote) the live PR state — number, open/draft/merged, a check-run summary (checks: ok, checks: 2/3, or checks: 1 failing), and the review decision. The current branch is marked with *. When a branch has an open/merged PR, the base column reflects the PR's actual target; otherwise it's the base ship would use. Stacked children are indented under their parent.
BRANCH BASE STATE PR
* feature/new-nav main published #42 · open · checks: 2/3 · review pending
feature/api staging published #40 · open · checks: ok · approved
└─ feature/ui feature/api published #41 · open · checks: ok · review pending
feature/spike develop local only —Interactive board. In a terminal, list is a keyboard-driven cockpit rather than a static report — arrow keys (or j/k) move the selection, and you act on the highlighted branch without leaving the board:
| key | action |
|-----|--------|
| o / Enter | open the PR (or its compare page) in the browser |
| c | check out the branch |
| s | ship it (git-shit ship) |
| m | merge it (git-shit merge) |
| r | refresh the data |
| q | quit |
Pass --plain (or pipe/redirect the output) for the static table above — that's what scripts and non-terminals get automatically.
One git ls-remote (publish state) and one gh pr list (PR state) back the whole table; they run in parallel behind a progress spinner, so the wait is the slower of the two (not their sum) and you always see it's working. status, ship, and merge show the same spinner while they talk to GitHub. On Bitbucket, or without gh, the PR columns are omitted.
completion <bash|zsh|fish>
Prints a shell-completion script for git-shit to stdout — completes subcommands and their flags. Load it from your shell config:
# bash — in ~/.bashrc
source <(git-shit completion bash)
# zsh — in ~/.zshrc (after compinit)
source <(git-shit completion zsh)
# fish
git-shit completion fish > ~/.config/fish/completions/git-shit.fishConfiguration
The default PR target — used by ship, merge-cleanup, done, and status when a branch has no base recorded by start and you don't pass an explicit dest — is staging. Change it per-repo (or everywhere with --global):
git config gitshit.base develop # this repo
git config --global gitshit.base develop # all reposPrecedence, highest first: an explicit dest argument (git-shit ship main) → the base recorded on the branch by git-shit start <name> <base> → gitshit.base → the built-in default staging.
Default reviewers, labels, and assignees
Set defaults that ship applies to every new PR (GitHub + gh). Values are comma-separated; any --reviewer=/--label=/--assignee= flag on a given ship is unioned in on top.
git config gitshit.reviewers alice,bob
git config gitshit.labels feature,needs-qa
git config gitshit.assignees @meRequirements
- git; git-flow is only needed for
git-shit start—ship,merge, anddonework without it. If you do use git-flow, initialise it with feature prefixfeature/(git flow init) - A Bitbucket or GitHub
originremote - Node.js >= 16
- For terminal PRs on GitHub: the GitHub CLI (
gh), logged in viagh auth login - For the browser fallback auto-click: macOS + Google Chrome (on other platforms the PR page still opens; you click Create yourself)
One-time Chrome setup (browser fallback only)
Only needed if you use the browser flow (Bitbucket, no gh, or ship --web):
Chrome menu bar → View → Developer → Allow JavaScript from Apple Events, then fully quit Chrome (Cmd+Q) and reopen it.
Your terminal also needs Automation permission for Chrome: System Settings → Privacy & Security → Automation → enable Google Chrome under your terminal app. Without either, the script still opens the PR page — you just click Create yourself.
License
MIT
