Metadata-Version: 2.4
Name: git-split-cli
Version: 0.1.1
Summary: Carve a messy multi-file Git branch into clean, reviewable commits
Author-email: Sophie Nguyen <sophie.nguyenthuthuy@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/sophie-nguyenthuthuy/git-split
Keywords: git,cli,commits,diff,developer-tools
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Version Control :: Git
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# git-split

**Turn one messy branch into a sequence of commits a reviewer can understand.**
`git-split` compares the current branch with its merge base, groups related files
and hunks, lets you reshape the proposal, then builds the commits on a new branch.
The original branch, working tree, and index stay untouched.

Pure Python standard library at runtime. Git is the only external requirement.

![git-split demo](docs/demo.gif)

<sub>Demo scripted and rendered with [termreel](https://github.com/sophie-nguyenthuthuy/termreel).</sub>

<sub>Recorded with <a href="https://github.com/sophie-nguyenthuthuy/termreel">termreel</a>; the reproducible source is <a href="docs/demo.tape">docs/demo.tape</a>.</sub>

```console
$ git split --dry-run
analyzing feature vs main (6 file(s), 7 hunk(s))
plan: 3 commit(s)
  1. refactor(assets): update logo.bin  [1 file(s), +0 -0]
  2. docs(docs): update guide.md  [1 file(s), +2 -0]
  3. feat(src): add cli.py, helpers.py, parser.py and 1 more  [4 file(s), +8 -4]

commit 1: refactor(assets): update logo.bin
  assets/logo.bin * | binary
...
dry run: no branch created
```

## Install

```bash
python -m pip install git-split-cli
```

The installed `git-split` executable is also available as `git split` because
Git discovers executables named `git-*` on `PATH`.

The PyPI distribution is named `git-split-cli`; the shorter `git-split`
distribution name belongs to an unrelated project.

## Use

From the messy branch:

```bash
git split                    # edit the proposal in $EDITOR, then execute
git split --edit prompt      # use the built-in line-oriented editor
git split --dry-run          # inspect the proposal; change nothing
git split --base origin/main # choose the comparison base explicitly
git split --branch clean-pr  # choose the output branch name
```

By default the tool detects `origin/HEAD`, `origin/main`, `origin/master`,
`main`, `master`, or `develop`, then compares `HEAD` with the selected ref's
merge base. If the proposed output name already exists, a numeric suffix is
added instead of overwriting it.

For a reviewable two-step workflow, write the plan to disk and execute it later:

```bash
git split --write-plan split.plan
$EDITOR split.plan
git split --use-plan split.plan
```

The plan resembles an interactive-rebase todo file. Move hunk lines between
commit blocks, reorder blocks, and edit messages. Validation rejects plans that
drop or duplicate any change.

```text
commit 1: fix(parser): normalize tokens
  src/parser.py #1 | @@ -8,7 +8,7 @@ def parse(text):

commit 2: refactor(output): render values with repr
  src/parser.py #2 | @@ -24,7 +24,7 @@ def emit(items):
```

Whole-file entries use `*`. Binary files, renames, additions, and deletions are
atomic and always appear this way.

## How grouping works

Candidate commits combine four deterministic signals:

- directory proximity;
- identifier overlap in changed lines;
- Python and JavaScript/TypeScript import relationships;
- files that changed together in recent history.

After file clustering, individual hunks can move to a better-matching cluster.
Use `--file-level` to disable hunk splitting, `--threshold` to tune clustering,
or edit the plan directly when intent beats heuristics.

## Safety model

Execution creates a temporary Git worktree at the merge base and stages each
planned patch with `git apply --cached`. Commits are created only on the new
branch. If reordered hunks conflict, that file is regrouped into its earliest
commit and execution restarts. If execution still fails—or if the final tree is
not byte-identical to the original tip—the incomplete branch is removed.

`--dry-run` does not create a branch or commit. Hooks are skipped for generated
commits so local hooks cannot modify the reconstructed tree.

## Options

```text
--base REF          base ref (otherwise auto-detected)
--branch NAME       output branch (default: <current>-split)
--dry-run           print the plan and make no changes
--edit MODE         auto, editor, prompt, or none
--threshold N       clustering threshold from 0 to 1
--file-level        keep every file atomic
--write-plan FILE   save a plan and exit
--use-plan FILE     validate and execute an edited plan
--history-limit N   history commits used for co-change analysis
-y, --yes           skip confirmation
```

## Development

```bash
python -m pytest -q
# or
make test
```

The tests build real repositories in temporary directories and exercise binary
patches, renames, hunk reordering, fallback behavior, editor validation, dry
runs, and preservation of the source branch.

MIT license.
