New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

ghls-stack-pr

Package Overview
Dependencies
Maintainers
1
Versions
7
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ghls-stack-pr

Stacked PR CLI — make small, reviewable PRs the default workflow

latest
Source
npmnpm
Version
0.2.0
Version published
Maintainers
1
Created
Source

⚡ ghls

Git, but with Stacked PR Superpowers

Stop shipping 2,000-line PRs that nobody wants to review.

npm version license node


  ┌─────────────────────────────────┐
  │  ⚡ G H L S                     │
  │  Git + Stacked PRs, Supercharged│
  └─────────────────────────────────┘

Quick Start · Commands · How It Works · Configuration · GitHub Actions

What is this?

ghls is a drop-in replacement for git that adds stacked PR superpowers on top. Every command you already know (ghls status, ghls commit, ghls log) passes straight through to git. But when you need to ship a stack of small, reviewable PRs — that's where the magic kicks in.

You write code normally. ghls does the rest.

  ┌─ #4 fix: edge case in notifications     +12 -3   ✅ CI passing
  ├─ #3 feat: notification preferences       +45 -12  🔄 In review
  ├─ #2 feat: user settings page             +120 -30 ✅ Approved
  └─ #1 feat: settings data model            +80 -5   ✅ Approved
  ▼
  main

When PR #1 gets merged, run ghls land and #2, #3, #4 automatically rebase onto the new main. No manual branch juggling. No merge conflicts from stale bases. Just vibes.

Why Stacked PRs?

Mega PRStacked PRs with ghls
1,500 lines in one shot3 focused PRs of ~200 lines each
"LGTM" after 2 days of procrastinationMeaningful review in minutes
Merge conflicts from hellAuto-rebase keeps everything fresh
Blocks the whole teamLand PRs independently as they're approved
Reviewer: 😩Reviewer: 😎

🚀 Quick Start

Prerequisites

Before installing ghls, make sure you have:

DependencyVersionHow to checkInstall
Node.js>= 18node --versionnodejs.org
GitHub CLIanygh --versioncli.github.com
Gitanygit --versionYou probably have this already

Important: Make sure you're authenticated with GitHub CLI: run gh auth status to check. If not, run gh auth login.

Install

npm install -g ghls-stack-pr

Your first stack in 60 seconds

# 1. Navigate to any GitHub repo
cd your-repo

# 2. Initialize ghls (one-time setup, takes 5 seconds)
ghls init

# 3. Write code and make commits as usual
git add .
git commit -m "feat: add settings data model"
git commit -m "feat: add settings UI"

# 4. Ship it as stacked PRs
ghls submit

# 5. See your beautiful stack
ghls stack

# 6. After PR #1 is approved and merged on GitHub...
ghls land     # rebases the rest onto main automatically

That's it. You're stacking. 🎉

Pro tip: alias git to ghls

Since ghls passes unknown commands to git, you can replace git entirely:

# Add to your ~/.zshrc or ~/.bashrc
alias git=ghls

Now git commit, git push, git log all work normally, but you also get git submit, git stack, git land for free.

📖 Commands

Stack Commands

ghls init

One-time repo setup. Creates a .ghls.yml config file, detects your GitHub remote, and verifies gh is installed.

ghls init

ghls submit (alias: up)

Creates or updates stacked PRs from your commits. The heart of ghls.

ghls submit                          # interactive mode
ghls submit --one-click              # fully automated, no prompts
ghls submit --draft                  # create as draft PRs
ghls submit --reviewer alice,bob     # assign reviewers
ghls submit --dry-run                # see the plan without executing
ghls submit --force                  # bypass size limits
ghls submit --group semantic         # choose grouping strategy
FlagShortDescription
--one-clickNon-interactive mode — auto-groups, skips prompts, uses defaults
--draft-dCreate PRs as drafts
--reviewer <names>-rComma-separated list of GitHub usernames to request review from
--yes-ySkip confirmation prompts
--forceBypass size validation limits
--dry-runShow what would happen without doing it
--group <strategy>-gGrouping strategy (see Grouping Strategies)

ghls land (alias: merge)

Merges the bottom PR(s) and rebases the rest of the stack.

ghls land                            # merge bottom PR (interactive)
ghls land --one-click                # merge all, no prompts
ghls land --all                      # merge entire stack
ghls land --count 2                  # merge bottom 2 PRs
ghls land --dry-run                  # preview the plan
ghls land --conflict theirs          # auto-resolve conflicts
FlagShortDescription
--one-clickEquivalent to --all --yes — merges everything non-interactively
--allLand all PRs in the stack (bottom-up)
--count <n>-nNumber of PRs to land from the bottom
--yes-ySkip confirmation
--dry-runShow the merge plan without executing
--conflict <strategy>Conflict resolution: manual, ours, theirs, abort

ghls sync

Rebases your entire stack onto the latest base branch. Run this when main has been updated.

ghls sync                            # rebase stack onto latest main
ghls sync --push                     # also force-push branches after sync
ghls sync --conflict ours            # auto-resolve with local changes
ghls sync --continue                 # continue after manual conflict resolution
ghls sync --abort                    # abort an in-progress rebase
FlagDescription
--pushForce-push branches after rebase
--forceOverwrite remote branches even if they're ahead
--conflict <strategy>manual (default), ours, theirs, abort
--continueResume after you've resolved conflicts manually
--abortCancel the in-progress rebase and rollback

ghls stack (alias: sk)

Visualize your current stack. Read-only, never modifies anything.

ghls stack                           # show stack overview
ghls stack --size                    # include detailed size breakdown
ghls stack --no-remote               # skip fetching PR info from GitHub

Example output:

  Stack: 3 PRs on main

  ┌─ #103  feat: notification preferences   +45 -12   🔄 Review pending
  ├─ #102  feat: user settings page          +120 -30  ✅ Approved
  └─ #101  feat: settings data model         +80 -5    ✅ Approved, CI passing
  ▼
  main (2 commits behind)

ghls abandon

Close all PRs in the stack, delete remote branches, and clean up metadata.

ghls abandon                         # interactive confirmation
ghls abandon --keep-local            # close PRs but keep local branches/metadata

Utility Commands

ghls config

View or update configuration.

ghls config --show                   # display all merged config
ghls config mergeMethod              # get a single value
ghls config mergeMethod rebase       # set a value
ghls config maxPrSize 400 --global   # set in user-level config
FlagDescription
--showDisplay the fully merged configuration
--globalRead/write from user-level config (~/.config/ghls/config.yml)

ghls stats

Your personal stacked PR analytics dashboard.

ghls stats

Shows: total commands run, stacks created, PRs created & landed, sync count, conflicts resolved, average PR size trends, weekly size charts, and estimated review time savings.

ghls history

View the operation log — every submit, land, sync, and abandon is recorded with timestamps and backup references.

ghls history                         # show recent operations

ghls undo

Revert the last ghls operation by restoring from its automatic backup.

ghls undo                            # restore previous state

ghls cleanup

Remove old backup branches (older than 7 days).

ghls cleanup

Command Aliases Cheat Sheet

ghls submit  →  ghls up
ghls stack   →  ghls sk
ghls land    →  ghls merge

🔧 How It Works

The Core Idea

Each commit on your working branch becomes its own PR. PRs are chained so that each one's base is the previous PR's branch:

Your commits:          The PRs ghls creates:

  commit C             PR #3 (base: PR #2's branch)
  commit B             PR #2 (base: PR #1's branch)
  commit A             PR #1 (base: main)

This means reviewers see small, focused diffs — not the entire feature at once.

Metadata: How ghls Tracks the Stack

ghls stamps git trailers on your commits to track everything:

feat: add settings data model

ghls-id: a1b2c3d4-5678-9abc-def0-1234567890ab
ghls-stack: e5f6a7b8
ghls-order: 1
TrailerPurpose
ghls-idUnique ID per commit (UUID)
ghls-stackShared identifier for the entire stack (8-char UUID prefix)
ghls-orderPosition in the stack (1 = bottom, first to merge)

These trailers are added automatically during ghls submit and are used for stack detection, ordering, and integrity validation.

Branch Naming

ghls creates branches using a configurable template:

Default: ghls/{user}-{n}

Example for user "alice" with a 3-PR stack:
  ghls/alice-1  →  PR #1
  ghls/alice-2  →  PR #2
  ghls/alice-3  →  PR #3
PlaceholderValue
{user}Your GitHub username
{stack}Stack ID (8-char UUID)
{n}Position in stack (1-based)

Git Passthrough

Any command ghls doesn't recognize is forwarded directly to git:

ghls status      →  git status
ghls commit -m   →  git commit -m
ghls log --oneline  →  git log --oneline
ghls diff        →  git diff
ghls push        →  git push  (with a helpful hint if you're in a stack)

ghls intercepts push on stack branches to suggest using ghls submit instead, but still runs the push if you want it.

⚙️ Configuration

Config File

ghls init creates .ghls.yml in your repo root:

baseBranch: main
branchTemplate: "ghls/{user}-{n}"
mergeMethod: squash              # squash | merge | rebase
maxPrSize: 500                   # hard block above this
warnPrSize: 300                  # warning above this
maxStackEntries: 30              # max PRs in one stack
excludePatterns:
  - "*.lock"
  - "*.generated.*"
  - "package-lock.json"
  - "yarn.lock"
  - "pnpm-lock.yaml"
groupingStrategy: single         # single | marker | semantic | file | interactive | auto
autoSync: false                  # auto-sync before submit
analytics: true                  # local usage analytics
remote: origin                   # GitHub remote name

Config Hierarchy

Configuration is merged in this order (later wins):

  ┌─────────────────────────────────────────────────────┐
  │  CLI flags                              (highest)   │
  │  .ghls.yml (repo)                                   │
  │  ~/.config/ghls/config.yml (user)                   │
  │  Built-in defaults                      (lowest)    │
  └─────────────────────────────────────────────────────┘

This means you can set personal defaults globally and override per-repo.

All Config Options

OptionDefaultDescription
baseBranchmainThe branch your stack targets
branchTemplateghls/{user}-{n}Branch naming pattern
mergeMethodsquashHow PRs are merged: squash, merge, or rebase
maxPrSize500Hard block — PRs above this line count are rejected
warnPrSize300Warning threshold — you'll get a heads-up
maxStackEntries30Maximum number of PRs in a single stack
excludePatterns*.lock, *.generated.*, etc.Glob patterns excluded from size counting
groupingStrategysingleHow commits are grouped into PRs
autoSyncfalseAutomatically rebase onto base before submit
analyticstrueCollect local usage analytics
remoteoriginName of the GitHub remote

📏 Size Validation

ghls keeps your PRs small by default. Not all lines are created equal, so different file types are weighted differently:

  File Type        Weight    Example
  ─────────        ──────    ───────
  Code             1.0x      src/api/users.ts
  Tests            0.5x      tests/users.test.ts
  Migration        0.7x      migrations/001_add_users.sql
  Config           0.3x      tsconfig.json
  Docs             0.2x      docs/api.md
  Generated        0.0x      package-lock.json (excluded)

How It Works

  • ghls calculates the weighted line count for each PR
  • Lines in excludePatterns files are ignored entirely
  • The weighted total determines the verdict:
  Weighted lines < 300  →  ✅ Good to go
  Weighted lines < 500  →  ⚠️  Warning (still submits)
  Weighted lines > 500  →  🚫 Blocked (use --force to override)
  • Review time estimate: ~200 weighted lines per hour

Use ghls stack --size for a full breakdown by category.

🔀 Grouping Strategies

When you have 2+ new commits, ghls submit shows an interactive picker:

  How should these new commits become PRs?
    1  Each commit = 1 PR (single)
    2  Group all into 1 PR
    3  Use suggested grouping above        ← only shown if ghls found a smarter split
    4  Group by [stack] markers
    5  Group by type (feat/fix/refactor)
    6  Group by file scope (directory)
    7  Interactive — manually assign commits to PRs
#StrategyBehavior
1singleEach commit becomes its own PR (default)
2all-in-oneEvery commit is squashed into a single PR — you pick the title
3auto (suggested)ghls analyzes marker/semantic/file strategies and suggests the best split. Only appears when a non-trivial grouping exists
4markerGroups commits that share a [stack:name] or [group:name] tag in their message
5semanticGroups consecutive commits with the same conventional commit type (feat, fix, refactor, etc.)
6fileGroups commits by top-level directory scope — commits touching the same area go together
7interactiveShows all commits numbered, you type ranges (e.g. 1,2,3 or 1-3) to assign them to PRs manually

With --one-click, ghls skips this prompt and uses the auto strategy automatically.

Set a default in .ghls.yml or override per-submit:

ghls submit --group semantic         # skip the picker, use semantic
ghls submit --group file             # skip the picker, group by directory
ghls submit --one-click              # auto strategy, no prompts at all

🔥 Conflict Resolution

When a rebase produces conflicts, ghls gives you options:

StrategyWhat happens
manualDrops you into your editor to resolve, then ghls sync --continue
oursKeeps your local changes, discards incoming
theirsAccepts incoming changes, discards yours
abortCancels the rebase entirely and rolls back

ghls includes an interactive conflict wizard that analyzes the conflicts, shows their difficulty, and lets you pick a strategy. It handles multiple conflict rounds in a loop until everything's resolved.

# Automatic resolution
ghls sync --conflict theirs

# Manual resolution workflow
ghls sync                    # hits a conflict
# ... fix the files ...
git add .
ghls sync --continue         # resume

🤖 GitHub Actions

ghls ships 4 optional workflows. Copy them from the actions/ directory into your repo's .github/workflows/:

cp node_modules/ghls-stack-pr/actions/*.yml .github/workflows/

auto-rebase.yml

Trigger: PR merged

Automatically detects when a ghls-managed PR is merged, finds dependent PRs in the stack, updates their base branches, and attempts an auto-rebase. Comments on the PR with success/failure status.

stack-validate.yml

Trigger: PR opened or updated

Validates stack integrity — checks that base branch chaining is correct and the stack metadata is consistent. Posts a validation comment on the PR.

ci-optimize.yml

Trigger: After auto-rebase completes

Cancels redundant CI runs when branches get force-pushed. Groups workflow runs by branch, keeps the latest, cancels everything older. Saves your CI minutes.

review-preserve.yml

Trigger: PR force-pushed (sync event)

Detects "cosmetic" rebases (same tree hash, different commit hash) and re-requests reviews from previous approvers. This way, approvals aren't lost just because ghls rebased the stack.

🛡️ Safety

ghls is paranoid about your code. Here's how it keeps you safe:

FeatureDescription
Automatic backupsEvery destructive op (submit, land, sync, abandon) creates a backup branch first
Undoghls undo restores the state from the last backup
Abortghls sync --abort cancels an in-progress rebase
Dry run--dry-run on submit and land shows the plan without doing anything
Scoped force-pushOnly force-pushes ghls-managed stack branches, never your main branch
Non-destructiveWorks alongside regular git push — only touches its own PRs and branches
Operation logEvery operation is logged in ~/.config/ghls/history/operations.json
Backup cleanupghls cleanup removes backups older than 7 days (max 50 entries)

Backup branches are stored as ghls-backup/{branch}/{operation}-{timestamp}.

📊 Analytics

ghls collects local-only usage analytics (nothing leaves your machine). Run ghls stats to see:

  • Total commands run, stacks created, PRs shipped & landed
  • Sync count and conflicts resolved
  • Average & median PR sizes over time
  • Weekly size trend charts
  • Estimated review time savings

Disable with ghls config analytics false or set analytics: false in your config.

🧑‍💻 Full Workflow Example

Here's a real-world scenario: you're building a notifications feature.

# Start from main
git checkout main && git pull

# Create a feature branch
git checkout -b notifications

# Write the data layer
# ... code code code ...
git add . && git commit -m "feat: add notification data model"

# Write the API
# ... code code code ...
git add . && git commit -m "feat: add notification API endpoints"

# Write the UI
# ... code code code ...
git add . && git commit -m "feat: add notification bell component"

# Ship it as 3 stacked PRs
ghls submit
#  → Creates PR #1: notification data model (base: main)
#  → Creates PR #2: notification API (base: PR #1)
#  → Creates PR #3: notification bell (base: PR #2)

# Check your stack
ghls stack
#  ┌─ #103  feat: add notification bell component    +95 -10
#  ├─ #102  feat: add notification API endpoints      +140 -25
#  └─ #101  feat: add notification data model         +60 -3
#  ▼
#  main

# PR #1 gets approved and merged on GitHub...

# Land it and rebase the rest
ghls land
#  → Merges #101
#  → Rebases #102 onto main, updates base
#  → Rebases #103 onto #102, updates base
#  → Force-pushes updated branches

# PR #2 gets approved...
ghls land
# ...and so on until the stack is empty 🎉

🛠️ Development

git clone <repo>
cd ghls-stack
npm install
npm run build
npm link            # makes `ghls` available globally
npm run dev         # watch mode (tsc --watch)
npm test            # vitest
npm run test:watch  # vitest in watch mode
npm run lint        # eslint

Project Structure

ghls-stack/
├── bin/ghls.ts              # CLI entrypoint & git passthrough
├── src/
│   ├── commands/            # Command implementations
│   │   ├── init.ts          # ghls init
│   │   ├── submit.ts        # ghls submit
│   │   ├── land.ts          # ghls land
│   │   ├── sync.ts          # ghls sync
│   │   ├── status.ts        # ghls stack
│   │   ├── abandon.ts       # ghls abandon
│   │   ├── config.ts        # ghls config
│   │   ├── history.ts       # ghls history, undo, cleanup
│   │   └── stats.ts         # ghls stats
│   ├── core/                # Core logic
│   │   ├── stack.ts         # Stack detection & ordering
│   │   ├── git.ts           # Git operations
│   │   ├── github.ts        # GitHub API (via gh CLI)
│   │   ├── conflicts.ts     # Conflict resolution wizard
│   │   ├── backup.ts        # Backup/restore system
│   │   └── metadata.ts      # Commit trailer management
│   ├── validation/          # PR validation
│   │   ├── size.ts          # Size analysis & weighting
│   │   ├── rules.ts         # Pre-submit/pre-land checks
│   │   └── grouping.ts      # Commit grouping strategies
│   ├── config/              # Configuration
│   │   ├── schema.ts        # Zod config schema
│   │   └── loader.ts        # Config hierarchy merger
│   ├── ui/                  # Terminal UI
│   │   ├── tree.ts          # ASCII stack visualization
│   │   ├── prompts.ts       # Interactive prompts
│   │   ├── stages.ts        # Progress stages
│   │   └── spinner.ts       # Loading spinners
│   ├── analytics/           # Analytics
│   │   ├── collector.ts     # Metrics tracking
│   │   └── storage.ts       # PR history & trends
│   └── utils/               # Utilities
│       ├── errors.ts        # Custom error types
│       ├── exec.ts          # Process execution
│       └── logger.ts        # Logging
├── actions/                 # GitHub Actions workflows
├── tests/                   # Unit & integration tests
└── .ghls.yml                # Repo config (created by init)

FAQ

Q: Does ghls modify my existing branches? No. ghls only creates and manages its own branches (prefixed with ghls/). Your working branch is untouched.

Q: Can I use ghls with an existing PR? ghls manages its own stack. If you have a regular PR, keep it. Start fresh with ghls submit for new work.

Q: What if I make more commits after submitting? Run ghls submit again. It updates existing PRs or creates new ones as needed.

Q: Does it work with GitLab/Bitbucket? Not yet — ghls depends on the GitHub CLI (gh). GitHub only for now.

Q: What merge methods are supported? squash (default), merge, and rebase. Set via mergeMethod in your config.

Q: Can I have multiple stacks at once? Each branch gets its own stack. Switch branches to work on different stacks.

License

MIT — go build cool things.

Keywords

git

FAQs

Package last updated on 22 Mar 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts