Thanks for your interest in contributing to ez! This guide covers everything you need to get started.
# Clone the repo
git clone https://github.com/rohoswagger/ez-stack.git
cd ez-stack
# Build
cargo build
# Run tests
cargo test
# Run the CLI locally
cargo run -- <command>You'll also need git and gh (GitHub CLI) installed and authenticated for integration tests.
We enforce consistent style with standard Rust tooling:
# Format code
cargo fmt
# Lint
cargo clippy -- -D warningsBoth checks run in CI. Please run them locally before submitting a PR.
- Keep functions short and focused.
- Prefer returning
Resultover panicking. - Write doc comments for all public types and functions.
- Use
thiserrorfor error types. Avoidanyhowin library code.
- Fork the repository and create a branch from
main. - Make your changes, add tests where appropriate.
- Run
cargo fmt,cargo clippy, andcargo test. - Open a pull request against
mainwith a clear description of what you changed and why.
For larger changes, please open an issue first to discuss the approach.
src/
├── main.rs # Entry point and command dispatch
├── cli.rs # CLI argument parsing (clap derive)
├── cmd/ # One module per command (create.rs, sync.rs, push.rs, etc.)
├── stack.rs # Stack state model and persistence (.git/ez/stack.json)
├── git.rs # Git shell-out operations
├── github.rs # Wrapper functions for shelling out to gh
├── ui.rs # Terminal colors, spinners, tree rendering
└── error.rs # thiserror error types
| File | Purpose |
|---|---|
src/main.rs |
Entry point and command dispatch. |
src/cli.rs |
CLI argument parsing using clap derive macros. |
src/stack.rs |
Defines the Stack and Branch structs. Handles serialization and deserialization of .git/ez/stack.json. |
src/git.rs |
Thin wrappers around git commands (rebase, checkout, branch, etc.) using std::process::Command. |
src/github.rs |
Thin wrappers around gh commands (pr create, pr edit, pr view, etc.). |
src/ui.rs |
Terminal colors, spinners, and tree rendering for stack visualization. |
src/error.rs |
Error types defined with thiserror. |
src/cmd/ |
Each command is a module that takes parsed CLI args and orchestrates calls to stack, git, and github. |
We deliberately call the git and gh CLIs as subprocesses rather than using libgit2 or the GitHub API directly. Reasons:
- Transparency. Users can see exactly what commands ran. If something goes wrong, they can reproduce or fix it with the same tools.
- Correctness.
git rebasehas complex behavior around conflict resolution, hooks, and config. Reimplementing it is a source of bugs. Shelling out gets us the real thing. - Simplicity. No OAuth token management, no REST/GraphQL client, no C bindings.
ghhandles auth and API versioning for us.
The tradeoff is a dependency on both CLIs being installed and some overhead from process spawning, which is negligible for a developer tool.
- Stored inside
.git/so it's never accidentally committed. - Single file so reads and writes are atomic (via write-to-temp + rename).
- JSON so it's human-readable and easy to debug. If
ezever gets into a bad state, you can edit the file by hand. - Versioned with a
"version"field so we can migrate the format in the future without breaking existing repos.
When a parent branch is updated (e.g., after a sync), child branches need to move. git rebase --onto is the precise tool for this:
git rebase --onto <new-parent> <old-parent> <child>
This replays only the commits unique to <child> onto <new-parent>, which is exactly the semantics we want when restacking.
# Unit tests
cargo test
# Run a specific test
cargo test test_stack_serialization
# Integration tests (requires git and gh)
cargo test --test integrationIntegration tests create temporary git repos and exercise full command flows. They do not touch GitHub — any gh calls are stubbed.
Open an issue or start a discussion on the repository. We're happy to help.