Real development is rarely linear. You're mid-feature when you spot a typo in the README, or a stray debug statement left over from last week. Before long your working directory is a pile of loosely related edits, and the staging area gives you exactly one place to put them.
I ran into this problem when my team migrated from Subversion to Git. SVN had changelists: named groups of files you could organise before committing. I had changelists like ready_to_commit and do_not_commit, and the clarity they gave me was something I immediately missed in Git.
So I built git-cl, a Git subcommand that adds changelists to your workflow.
What's a changelist, exactly?
Think of changelists as sticky notes for your working directory. You group modified files under a name before deciding what to stage or commit. Nothing is staged until you say so, and you can have as many changelists as you like, all at once.
Changelists sit between your working directory and Git's staging area. They don't touch Git's index or history until you explicitly stage or commit. They're stored locally in .git/cl.json, private to your workspace and never committed.
Install with:
pip install git-changelists
Workflow 1: Named staging areas
Git gives you one staging area. Everything you git add lands there together, waiting for a single commit. That works fine when your changes all belong together, but less well when you're mid-feature and notice something unrelated that needs fixing.
Say you're implementing a feature and notice a typo in the README along the way:
git cl add feature src/core.py src/helpers.py tests/test_core.py
git cl add typo-fix README.md
git cl status shows you what's grouped where:
feature:
[ M] src/core.py
[ M] src/helpers.py
[ M] tests/test_core.py
typo-fix:
[ M] README.md
All of it lives in your working directory at once. You can run tests, keep editing, see the full picture. When you're ready, commit one changelist at a time:
git cl commit feature -m "Implement core feature"
git cl commit typo-fix -m "Fix typo in README"
Two focused commits, clean history, without ever touching a branch or juggling the staging area manually.
Workflow 2: Changelists as review stages
Here's where it gets more interesting. Changelists don't have to represent what you changed; they can represent where each file is in your review process.
Instead of grouping by feature, you group by stage. Start by putting all modified files into an initial changelist:
git cl add to_review src/core.py src/utils.py tests/test_core.py
Run your linter. Files that pass move forward:
git cl add linted src/core.py
Moving a file to a new changelist removes it from the old one automatically. git cl status becomes a dashboard of your review pipeline:
to_review:
[ M] src/utils.py
linted:
[ M] tests/test_core.py
ready:
[ M] src/core.py
You decide what the stages mean: numbered (review_1, review_2), named after what's been checked (linted, tested, profiled), or named after approvals (self_review_ok, ai_review_ok, ready_to_commit). The pattern is the same: changelists as a lightweight state machine, with git cl status as the dashboard.
Workflow 3: Late-binding branching
Branches are Git's model for isolating parallel work, but they ask you to decide upfront. In practice, you often don't know something needs its own branch until you're already deep into it.
git cl branch lets you defer that decision. Say you've been improving a numerical solver alongside other unrelated work:
git cl add solver-opt src/solver.f90 src/utils_math.f90
git cl add config-fix config.yaml
At some point the solver work has grown beyond the scope of your current branch. Rather than committing half-finished work, you promote it:
git cl branch solver-opt
git-cl stashes all active changelists, creates and checks out a new branch named solver-opt, then restores only the solver-opt changelist. Your other changelists stay stashed and can be recovered with git cl unstash.
The result: you're on a dedicated feature branch with your changelist intact, ready to pick up exactly where you left off. The branch decision followed the code, not the other way around.
Quick start
pip install git-changelists
# Inside any Git repo:
git cl add my-feature file1.py file2.py
git cl status
git cl commit my-feature -m "Add feature"
Full documentation and a step-by-step tutorial are at github.com/BHFock/git-cl.
Changelists aren't a replacement for branches. They're a layer that sits before them, a way to organise intent while work is still in motion. If your working directory often feels like a pile of loosely related edits, they might be worth a try.

Top comments (0)