sparse
is a powerful command-line tool that transforms how you work with Git by enabling elegant stacked pull request workflows. Break down complex features into smaller, manageable, and reviewable chunks while maintaining clear dependencies between them.
Traditional development often leads to massive PRs that are:
- π Hard to review - Reviewers get overwhelmed by large changesets
- β° Slow to merge - Complex changes take longer to get approved
- π Risky to deploy - Large changes increase the chance of bugs
- π Difficult to iterate - Feedback on one part blocks the entire feature
Stacked PRs solve this by:
- π¦ Breaking features into logical, atomic changes
- π Maintaining clear dependencies between related changes
- β‘ Enabling parallel development and review
- π― Making each PR focused and purposeful
Sparse creates a structured branching system that mirrors your feature's logical breakdown:
gitGraph
commit id: "main"
branch sparse/user/auth-system
checkout sparse/user/auth-system
commit id: "Feature: Auth System"
branch sparse/user/auth-system/database
checkout sparse/user/auth-system/database
commit id: "Add user table schema"
commit id: "Add migration scripts"
branch sparse/user/auth-system/api
checkout sparse/user/auth-system/api
commit id: "Add login endpoint"
commit id: "Add registration endpoint"
branch sparse/user/auth-system/frontend
checkout sparse/user/auth-system/frontend
commit id: "Add login form"
commit id: "Add registration form"
Each branch (called a "slice") represents a focused PR:
- Database slice: Schema changes and migrations
- API slice: Backend authentication endpoints
- Frontend slice: User interface components
- Git must be installed and available in your
PATH
- Zig (for building from source)
git clone <repository-url>
cd sparse
zig build
The binary will be available at zig-out/bin/sparse
.
Sparse uses Git configuration variables to identify users and create properly namespaced branches. Before using sparse, you need to configure your user identification.
Sparse needs to identify you to create unique branch names. It uses a hierarchical configuration approach:
- Primary:
sparse.user.id
(recommended) - Fallback:
user.email
(standard Git config)
# Recommended: Set a sparse-specific user ID
git config sparse.user.id "jane.doe"
# Alternative: Ensure your Git email is set (usually already configured)
git config user.email "jane.doe@company.com"
Config Variable | Purpose | Example | Required |
---|---|---|---|
sparse.user.id |
Primary user identifier for branch naming | jane.doe |
β Recommended |
user.email |
Fallback identifier if sparse.user.id not set | jane.doe@company.com |
The user identifier becomes part of your branch structure:
sparse/<user-id>/<feature-name>/slice/<slice-name>
Examples:
# With sparse.user.id = "jane.doe"
sparse/jane.doe/auth-system/slice/database
sparse/jane.doe/auth-system/slice/api
# With user.email = "jane.doe@company.com"
sparse/jane.doe@company.com/auth-system/slice/database
sparse/jane.doe@company.com/auth-system/slice/api
- Valid Branch Names: The identifier must be a valid Git branch name (no spaces, special characters limited)
- Team Consistency: Teams should agree on a naming convention (e.g., use email vs. username)
- Repository Scope: Configuration can be set globally or per-repository:
# Global configuration (affects all repositories)
git config --global sparse.user.id "jane.doe"
# Repository-specific configuration
git config sparse.user.id "jane.doe"
Check your configuration:
# Check sparse-specific config
git config sparse.user.id
# Check fallback email config
git config user.email
# View all sparse-related config
git config --get-regexp sparse
# Start a new feature targeting the main branch
sparse feature auth-system --to main
# Or create a feature with an initial slice
sparse feature auth-system database --to main
# Make some changes for your database layer
git add .
git commit -m "Add user table schema"
# Create a new slice for API work
sparse slice api
sparse status # to check dont be afraid to run this
sparse update # to update the slices in remote this does force-with-lease use with caution and make sure status doesnt report corruption
# Continue development...
git add .
git commit -m "Add authentication endpoints"
# Create another slice for frontend
sparse slice frontend
git add .
git commit -m "Add login components"
sparse status
This shows a beautiful overview of your feature:
ββ Sparse Feature Status
β
ββ π― Active Feature: auth-system
β
ββ π Slice Analysis:
β β Orphan slices: 1 (ideal: 1)
β β Forked slices: 0 (ideal: 0)
β
ββ π― Target Branch: main
β
ββ π³ Slice Graph:
β
β ββ π frontend β api
β ββ πΈ api β database
β ββ π± database β main
β
ββ π Merge Status:
β β database: not merged
β β api: not merged
β β frontend: not merged
β
ββ π Summary:
β π Total slices: 3
β β
Merged: 0 / 3
β π Pending: 3
β
ββ β¨ Status complete
Create or switch to a feature branch.
sparse feature [ options ] <feature_name> [<slice_name>]
args:
<feature_name>: name of the feature to be created. If a feature with the same name
exists, sparse simply switches to that feature.
<slice_name>: name of the first slice in newly created feature. This
argument is ignored if the feature already exists.
options:
--to <branch>: branch to build on top. Pull requests for the new
feature will target this branch. (default: main)
-h, --help: shows this help message.
Examples:
# Create a new feature
sparse feature user-dashboard --to main
# Create feature with initial slice
sparse feature payment-flow checkout --to develop
# Switch to existing feature
sparse feature user-dashboard
Create or switch to a slice within the current feature.
sparse slice [ options ] [<slice_name>]
args:
<slice_name>: name of the new slice in feature.
If the slice with the same name exists, then sparse switches to that slice.
If <slice_name> is not provided, then sparse creates the slice based on the number of slices currently in the feature.
options:
-h, --help: shows this help message.
Examples:
# Create auto-numbered slice
sparse slice
# Create named slice
sparse slice validation
# Switch to existing slice
sparse slice database
Show comprehensive status of the current feature.
sparse status [ options ]
Show status information for current feature
options:
-h, --help: shows this help message.
Displays:
- π Slice analysis and health checks
- π³ Visual slice dependency graph
- π Merge status for each slice
- π Summary statistics
Update and rebase all slices in the current feature.
sparse update [ options ] [<slice_name>]
Update all slices in current feature
options:
--continue: continue updating after fixing merge conflicts
-h, --help: shows this help message.
Let's walk through building a complete user authentication system:
sparse feature auth-system --to main
# Work on database schema
git add migrations/001_create_users.sql
git commit -m "Add users table with email and password fields"
git add models/user.js
git commit -m "Add User model with validation"
# you can either use git push or
# sparse update to push changes to remote
git push
# make sure to run sparse status whenever you feel like it
sparse slice api
git add routes/auth.js
git commit -m "Add POST /login and /register endpoints"
git add middleware/auth.js
git commit -m "Add JWT authentication middleware"
sparse slice frontend
git add components/LoginForm.jsx
git commit -m "Add login form component"
git add components/RegisterForm.jsx
git commit -m "Add registration form component"
sparse slice integration
git add tests/auth.integration.test.js
git commit -m "Add end-to-end authentication tests"
graph TD
A[main] --> B[database]
B --> C[api]
C --> D[frontend]
D --> E[integration]
B1[PR #1: Database Schema] -.-> B
C1[PR #2: API Endpoints] -.-> C
D1[PR #3: Frontend Components] -.-> D
E1[PR #4: Integration Tests] -.-> E
style B1 fill:#e1f5fe
style C1 fill:#f3e5f5
style D1 fill:#e8f5e8
style E1 fill:#fff3e0
- Keep slices focused: Each slice should have a single responsibility
- Logical dependencies: Ensure slices build on each other naturally
- Reasonable size: Aim for 50-300 lines changed per slice
- Regular status checks: Use
sparse status
to visualize your progress - Descriptive commits: Each commit should clearly describe its purpose
- Test each slice: Ensure each slice is functional on its own
sequenceDiagram
participant Dev as Developer
participant Git as Git Repository
participant CI as CI/CD
participant Rev as Reviewers
Dev->>Git: Push database slice
Git->>CI: Run tests for database
CI->>Rev: Request review for PR #1
Dev->>Git: Push API slice (depends on database)
Git->>CI: Run tests for API + database
CI->>Rev: Request review for PR #2
Rev->>Git: Approve & merge PR #1
Dev->>Git: Run `sparse update` to rebase unmerged slices
Git->>CI: Auto-rebase PR #2 onto main
Dev->>Git: Run `sparse update` to rebase unmerged slices
Rev->>Git: Approve & merge PR #2
# Continue the pattern...
Sparse uses a consistent naming convention:
sparse/<user.email>/<feature-name>/slice/<slice-name>
Example:
sparse/jane.doe@company.com/user-dashboard/slice/
βββ database # Database schema changes
βββ api # Backend API endpoints
βββ frontend # UI components
βββ integration # End-to-end tests
# When main branch gets updated
sparse update
# If conflicts occur
# ... resolve conflicts manually ... and then
sparse update --continue
Sparse provides helpful diagnostics:
sparse status
# Shows warnings if slice structure is corrupted
sparse update
# If conflicts occur, sparse pauses for manual resolution
git add resolved-files.js
git rebase --continue
sparse update --continue
- Focused reviews: Each PR has a clear, limited scope
- Faster turnaround: Smaller changes = quicker reviews
- Better context: Logical progression through the feature
- Parallel work: Multiple team members can work on different slices
- Early feedback: Get feedback on foundational changes before building on top
- Easier debugging: Issues are isolated to specific slices
- Faster delivery: Features ship incrementally
- Reduced risk: Smaller changes mean fewer bugs
- Better planning: Clear breakdown of feature complexity
This project is licensed under the LICENSE file in the repository.