git-cl is a command-line tool that brings changelists to Git — like sticky notes for your working directory. It lets you group files into named changelists before staging or committing, acting as a pre-staging review layer for managing multiple parallel changes with intent and clarity.
Changelists can be stashed selectively and promoted to dedicated branches — an approach called late-binding branching.
git-cl is a Git subcommand that lets you group related file changes under a named changelist — similar to changelists in Subversion, but tailored for Git workflows.
This helps when:
- Working on several features or fixes in parallel
- Wanting to break down a messy working directory into logical units
- Staging or committing changes by intent rather than file
Changelists are saved locally in .git/cl.json. They’re private to your workspace and not shared or committed. Think of them as quick labels on piles of work.
Read more: Why Why git-cl exists for the personal history, and the concept paper for design and related work.
Changelists act as a layer above Git's staging area — helping you group related edits before deciding what to stage or commit:
They work alongside Git's core concepts, not in place of them:
| Concept | Description | Role in Workflow |
|---|---|---|
| Changelist | A named group of modified files | Organise uncommitted work |
| Staging Area | Selects what goes into the next commit (Git docs) | Prepare commit content |
| Branch | A line of development with commit history (Git docs) | Isolate long-running or shared work |
Use changelists as your own labelled workspace, keep unrelated edits separate, or manage overlapping work — all without affecting Git history.
pip install git-changelists
This is the easiest method if you already have Python and pip available.
For the latest stable version, download directly from the git-cl repository:
mkdir -p ~/bin
wget https://raw.githubusercontent.com/BHFock/git-cl/main/git-cl -O ~/bin/git-cl
chmod +x ~/bin/git-cl
Make sure ~/bin is in your $PATH. You can add this line to your shell config file if needed:
export PATH="$HOME/bin:$PATH"
Clone the complete git-cl project including documentation and tests:
git clone https://github.com/BHFock/git-cl.git ~/opt/git-cl
chmod +x ~/opt/git-cl/git-cl
Add the cloned directory to your $PATH:
export PATH="$HOME/opt/git-cl:$PATH"
You can add this line to your shell profile (~/.bashrc, ~/.zshrc, etc.) to make it permanent.
Then confirm it's working:
git cl --version
git cl help
Git will recognise git-cl as a subcommand: you can now run git cl just like git commit or git status.
The following commands are the core of git-cl. Each helps you manage your changelists as you prepare your working directory for staging or commits.
git cl add <changelist-name> <file1> <file2> ...
- Groups files under a named changelist.
- A file can only belong to one changelist at a time; it will be moved if already assigned elsewhere.
git cl add docs README.md docs/index.md
git cl status
# or
git cl st
- Like git status --porcelain, but grouped by changelist.
- Shows Git’s precise two-letter status codes, grouped under each changelist.
- Files not assigned to any changelist appear under 'No Changelist'.
You can pass one or more changelist names to show only those specific groups:
git cl st docs tests
This limits the output to the specified changelists. By default, unassigned files (those in 'No Changelist') are hidden in this mode.
If you want to include unassigned files alongside named changelists, use the --include-no-cl flag:
git cl st docs --include-no-cl
By default, git cl status shows common status codes (like [M ], [??], [ D]) together with merge-conflict codes ([UU], [AA], etc.), since conflicts require your immediate attention. Less common codes such as type changes ([T ]) are hidden to keep the output readable.
To include every Git status code, use the --all flag:
git cl st --all
Example output of git cl st, showing files grouped by changelist with standard Git status codes.
If your repository has many untracked files that clutter the output, you can hide them with --no-untracked:
git cl st --no-untracked
This is useful when working on a focused task and you only want to see files already known to Git.
git cl diff <changelist-name>
git cl diff <changelist1> <changelist2>
git cl diff <changelist-name> --staged
- Shows a unified diff (git diff) of the files in the changelist.
- When you specify multiple changelists, shows a combined diff of all files from those changelists.
- Uses
git diff --cachedwhen--stagedis provided.
git cl diff docs # Show diff for 'docs' changelist only
git cl diff docs tests # Show combined diff for both 'docs' and 'tests'
git cl diff docs --staged # Show staged changes for 'docs' changelistgit cl stage <changelist-name>
- Stages all tracked files in the changelist.
- Untracked files ([??]) are ignored unless added with git add.
- By default, the changelist is kept unless
--deleteis used.
git cl stage docs
git commit -m "Refactor docs"
Tip: Run git cl diff first if you want to review changes.
git cl unstage <changelist-name>
- Unstages files from the changelist (i.e. removes them from the index).
- Only applies to staged files — unchanged or unstaged files are ignored.
- Files remain in the changelist and your working directory.
git cl unstage docs
This is useful when you've staged something too early and want to pull it back without losing the changelist group.
git cl commit <changelist-name> -m "Message"
git cl commit <changelist-name> -F commit.txt
- Automatically stages and commits all tracked files in the changelist — no need to run git add or git cl stage first.
- Untracked files (see status codes) are ignored unless you add them first with
git add. - The changelist is deleted after committing, unless you use
--keep.
Important: This command automatically stages tracked files before committing them, so you don't need to run git add or git cl stage first. However, untracked files ([??]) in the changelist are safely ignored and will remain untracked.
This allows you to commit grouped changes directly, without touching the Git staging area manually.
git cl commit tests -m "Add test environment"
# or
git cl commit tests -F message.txt
If you want to reuse the changelist (e.g. for further edits), use:
git cl commit tests -m "Partial commit" --keep
git cl remove <file1> <file2> ...
- Removes the given files from any changelist.
- Files remain unchanged in your working directory.
git cl delete <changelist1> <changelist2> ...
- Deletes one or more named changelists.
- Files remain in the working directory and will appear under “No Changelist” next time you run git cl st.
You can also delete all changelists at once with:
git cl delete --all
- This clears all changelists from your workspace, leaving files untouched.
Only changelist metadata is deleted — no file content or Git history is lost.
git cl checkout <changelist-name>
# or
git cl co <changelist-name>
- Reverts all files in the changelist to their last committed state (HEAD).
- Useful for discarding local changes by intent, not just by filename.
- Prompts for confirmation before proceeding.
- Shows a summary of reverted files.
You can explore all available commands directly from your terminal:
git cl help
To see help for a specific command — including its available options — add -h after the command name:
git cl commit -h
git cl branch -h
Note: git help cl does not work — because git-cl is an external subcommand and not part of Git's built-in help system. Always use git cl help instead.
These commands build on the Basic Commands - make sure you're familiar with adding files and viewing status first.
Sometimes you’re in the middle of a changelist but need to switch branches or pause your work — without committing it.
Stash saves those changes aside, and unstash brings them back later.
git cl stash <changelist-name>
Tip: Run stash commands from your repository root to avoid path issues.
- Saves all unstaged and untracked files from the changelist.
- Staged files are left alone (so you can commit them separately).
- The stash is named after your changelist and timestamped.
git cl unstash <changelist-name>
- Restores the previously stashed changes.
- Warns if files conflict with current working directory.
git cl stash --all
- Stashes all active changelists at once.
Tip: Your files aren’t deleted — they’re just “put in a box” until you bring them back with unstash.
You can turn a changelist into its own branch in one step — great for separating work mid-feature or starting a dedicated branch for a new idea.
git cl branch <changelist-name> [<branch-name>] [--from <base-branch>]
What happens under the hood:
- Saves (stashes) all active changelists.
- Creates and checks out the new branch.
- Restores only the chosen changelist on that branch — the other changelists remain stashed and can be restored with
git cl unstashlater.
See Section 4.3 for a worked example.
Changelists adapt to your mental model: group parallel tasks by intent, move files through a personal review pipeline, or promote a changelist to its own branch when it outgrows the workspace.
Git gives you one staging area. Everything you git add lands there, waiting for a single commit. That works fine when your changes all belong together — but less well when you’re mid-feature and notice a typo, some whitespace to clean up, or a stray print statement.
Changelists are groups of files you plan to commit together. They’re not commits themselves, and nothing about them touches Git’s index or history until you decide to stage and commit. Unlike the staging area, you can have as many changelists as you need, side by side, all labelled by intent.
Let’s say you’re implementing a feature across src/core.py, src/helpers.py, and tests/test_core.py, and along the way you notice a typo in the README and some whitespace to clean up in a utility file. Group them:
git cl add feature src/core.py src/helpers.py tests/test_core.py
git cl add typo-fix README.md
git cl add whitespace src/utils.py
All three sets of edits live in your working directory at once — you can run tests, see the full picture, keep editing. Nothing is staged yet. At any point, git cl status shows you what’s grouped where:
$ git cl status
feature:
[ M] src/core.py
[ M] src/helpers.py
[ M] tests/test_core.py
typo-fix:
[ M] README.md
whitespace:
[ M] src/utils.py
When you’re ready, stage and commit one changelist at a time:
git cl stage feature
git commit -m "Implement core feature"
git cl stage moves all three files of the feature changelist into Git’s staging area in one step; git commit then turns them into a single commit. You can also use git cl commit as a shortcut that stages and commits in one step:
git cl commit typo-fix -m "Fix typo in README"
git cl commit whitespace -m "Clean up whitespace in utils"
Three focused commits, each from its own changelist, without ever switching branches or shuffling the staging area manually.
This is where changelists shine over branches: when the work is small, related in context but unrelated in intent, and doesn’t warrant the overhead of a separate line of development. For larger or longer-running work, branches are still the right tool — and git cl branch helps when a changelist grows into something that deserves one.
Changelists can also track where each file is in your review process. Instead of grouping by feature, you group by review stage — files move from one changelist to the next as they pass each check.
Start with all modified files in review_1:
git cl add review_1 src/core.py src/utils.py tests/test_core.py
Do whatever your first check is — linter, formatting, self-review. As each file passes, move it on:
git cl add review_2 src/core.py
The file is now in review_2 and automatically removed from review_1. Continue through as many stages as you need. git cl status shows the state of every file at a glance:
$ git cl status
review_1:
[ M] src/utils.py
review_2:
[ M] tests/test_core.py
review_3:
[ M] src/core.py
When files reach the final stage, commit just that changelist:
git cl commit review_3 -m "Implement core feature"
What each stage means is up to you — numbered (review_1, review_2), named by what’s been checked (linted, tested, profiled), or by what’s been approved (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.
Sometimes you’re deep in the middle of a changelist — maybe tweaking numerical algorithms or refactoring simulation code — and you realise it would be cleaner to finish the work on a separate branch.
git cl branch makes that move painless, without committing incomplete work.
Let’s say you’re improving the performance of a weather simulation in Fortran. You’ve grouped your edits into a changelist:
git cl add solver-opt src/solver.f90 src/utils_math.f90
You’re not ready to commit, but you decide this optimisation deserves its own branch. Use:
git cl branch solver-opt
Your workspace will now look like you never left, but you’re on a dedicated branch:
$ git cl st
solver-opt:
[ M] src/solver.f90
[ M] src/utils_math.f90
This preserves your work-in-progress and the changelist grouping — so you can pick up right where you left off. Unlike git stash, git cl branch is changelist-aware. You don’t lose file intent or grouping across branches.
If you prefer a custom branch name or base branch, you can specify them:
git cl branch solver-opt feature/fortran-speedup --from main
This workflow is especially handy when experimental work — like optimising a finite-difference solver or changing file I/O routines — starts to grow beyond the original scope of your current branch. It keeps unrelated changes isolated and easy to review later.
For more step-by-step examples, each test script in the test suite can be exported as a standalone shell walkthrough you can follow in your terminal.
Just run git cl add with the new changelist name. You don't need to remove it from the old one manually.
git cl add new-list path/to/file
This automatically reassigns the file to new-list.
git cl commit always deletes the changelist after committing, unless you explicitly use the --keep flag:
git cl commit my-list -m "Implement my feature" --keep
git cl stage, git cl unstage, and git cl checkout behave differently — they keep the changelist by default. If you want to remove it in those cases, pass --delete:
git cl stage my-list
git cl unstage my-list
git cl checkout my-list --delete
This way, you can choose whether the changelist sticks around after staging or reverting.
git cl only stages or commits files already tracked by Git.
If a file is untracked ([??]), it will show up in git cl st, but won’t be included when staging or committing.
To include it:
git add my-file.txt
git cl stage my-changelist
No. Changelists are stored locally in .git/cl.json and are not version-controlled or shared via Git. They’re like personal sticky notes on your working directory and visible only to you.
Yes — changelists are local and independent of branches. This means:
- You can keep working on the same files across branches
- But beware: a changelist may reference files that don't exist in the new branch
If things get messy, delete a stale changelist:
git cl delete old-list
Use git cl stash <name>, then switch branches and git cl unstash when ready.
By default, git cl status shows the status codes you normally need to act on — common working-directory states plus merge conflicts — while hiding rare codes like [T ] (type change) to keep output clean.
To include every Git status code without filtering, use the --all flag:
git cl status --all
Yes. If the changelist was deleted after a stage or commit, you can create a new one with the same name — it's just a label, not a persistent identity.
No. A file can only belong to one changelist at a time. For patch-level splitting within a file, use git add -p alongside git-cl — the two tools complement each other.
git-cl doesn't have a dedicated command for this, but you can use git's own tooling:
git cl add my-changelist $(git diff --staged --name-only)
This is useful when you've already staged a meaningful set of changes and want to capture that grouping as a changelist. The files remain staged in git's index — if you also want to unstage them, follow up with git cl unstage my-changelist.
Yes. Each worktree has its own independent set of changelists — changes made in one worktree are not visible in another. This means you can use git-cl freely in each worktree without them interfering with each other.
Note: Many commands have short aliases (shown in "Alias" column) for faster typing.
| Task | Command | Alias |
|---|---|---|
| Add files to a changelist | git cl add <name> <files...> |
git cl a |
| View grouped status | git cl status [--all] [--no-untracked] [--no-color] |
git cl st |
| Show diff for changelist(s) | git cl diff <name1> [<name2> ...] [--staged] |
|
| Stage a changelist | git cl stage <name> [--delete] |
|
| Unstage a changelist | git cl unstage <name> [--delete] |
|
| Commit with inline message | git cl commit <name> -m "Message" [--keep] |
git cl ci |
| Commit using message from file | git cl commit <name> -F message.txt [--keep] |
|
| Revert changelist to HEAD | git cl checkout <name> |
git cl co |
| Remove files from changelists | git cl remove <file1> <file2> ... |
git cl rm |
| Delete changelists | git cl delete <name1> <name2> ... |
git cl del |
| Stash a changelist | git cl stash <name> |
|
| Unstash a changelist | git cl unstash <name> [--force] |
|
| Create branch from changelist | git cl branch <name> [<branch>] [--from <base>] |
git cl br |
| Show help | git cl help |
git cl status uses standard Git status codes — shown as two-character prefixes (e.g. [M ], [??]).
| Code | Status | Description |
|---|---|---|
[??] |
Untracked | New file, not yet tracked by Git |
[M ] |
Staged | Change staged and ready to commit |
[ M] |
Unstaged | Change made but not yet staged |
[MM] |
Mixed | Staged change with additional unstaged modification |
[A ] |
Added | New file added and staged |
[AM] |
Added + Modified | File added and then further modified |
[D ] |
Deletion (staged) | File deletion staged |
[ D] |
Deletion (unstaged) | File deleted but not yet staged |
[R ] |
Renamed | File renamed and staged |
[RM] |
Renamed + Modified | Renamed and then modified before staging |
[UU] |
Unmerged (conflict) | Both sides modified; resolve before staging |
To show all codes, including rare ones, use:
git cl status --all
| Code | Description |
|---|---|
[T ] |
Type change |
| Colour | Meaning |
|---|---|
| Green | Staged changes ([M ], [A ]) |
| Red | Unstaged changes ([ M], [ D]) |
| Red | Needs attention: unstaged changes ([ M], [ D]) or merge conflicts ([UU], [AA], …) |
| Magenta | Both staged and unstaged ([MM], [AM]) |
| Blue | Untracked ([??]) |
You can disable color output with:
git cl status --no-color
or set NO_COLOR=1 in your environment.
See also: Git status documentation
