Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
name: Test
on:
push:
tags: [v*]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
with:
python-version: ${{ matrix.python-version }}
- run: make test
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
- run: make lint
13 changes: 0 additions & 13 deletions .github/workflows/lint.yml

This file was deleted.

16 changes: 0 additions & 16 deletions .github/workflows/publish.yml

This file was deleted.

19 changes: 0 additions & 19 deletions .github/workflows/test.yml

This file was deleted.

2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.10
3.14
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
MIT License

Copyright (c) 2026 Kentaro Wada
Edits from Botify teams under Copyright (c) 2026 Botify teams

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
27 changes: 9 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
# git-hunk

[![PyPI](https://img.shields.io/pypi/v/git_hunk.svg)](https://pypi.org/project/git-hunk/)
[![Python](https://img.shields.io/pypi/pyversions/git_hunk.svg)](https://pypi.org/project/git-hunk/)
[![Build](https://github.com/wkentaro/git-hunk/actions/workflows/test.yml/badge.svg)](https://github.com/wkentaro/git-hunk/actions/workflows/test.yml)
[![License](https://img.shields.io/pypi/l/git_hunk.svg)](https://pypi.org/project/git-hunk/)
[![Build](https://github.com/botify-labs/git-hunk/actions/workflows/test.yml/badge.svg)](https://github.com/botify-labs/git-hunk/actions/workflows/test.yml)

Fork of https://github.com/botify-labs/git-hunk for Botify.

Non-interactive, programmatic alternative to `git add -p`.

Every hunk gets a stable, content-based ID so you can inspect, filter, and
stage changes without interactive prompts.

<img src="assets/teaser.png" alt="git-hunk teaser" width="800">

## Why?

`git add -p` requires interactive input. That makes it unusable for:
Expand All @@ -26,13 +23,7 @@ stage/unstage/discard commands.
## Install

```bash
pip install git-hunk
```

Or with [uv](https://docs.astral.sh/uv/):

```bash
uv tool install git-hunk
uv tool install git+https://github.com/botify-labs/git-hunk.git
```

Verify it works:
Expand All @@ -46,13 +37,13 @@ git-hunk --version
If you use [Claude Code](https://claude.ai/code), [Codex](https://openai.com/codex), or similar AI agents, install the skill from this repo:

```bash
npx skills add wkentaro/git-hunk
npx skills add botify-labs/git-hunk
```

Or via the [GitHub CLI](https://cli.github.com):

```bash
gh skill install wkentaro/git-hunk
gh skill install botify-labs/git-hunk
```

Or copy [`SKILL.md`](skills/git-hunk/SKILL.md) to
Expand Down Expand Up @@ -154,10 +145,10 @@ not the `@@` line numbers that shift as you stage hunks.
## Contributing

Bug reports, feature requests, and pull requests are welcome on
[GitHub](https://github.com/wkentaro/git-hunk).
[GitHub](https://github.com/botify-labs/git-hunk).

```bash
git clone https://github.com/wkentaro/git-hunk.git
git clone https://github.com/botify-labs/git-hunk.git
cd git-hunk
make setup # install dependencies
make test # run tests
Expand All @@ -166,4 +157,4 @@ make lint # run linters

## License

MIT ([LICENSE](https://github.com/wkentaro/git-hunk/blob/main/LICENSE))
MIT ([LICENSE](https://github.com/botify-labs/git-hunk/blob/main/LICENSE))
Binary file removed assets/teaser.png
Binary file not shown.
31 changes: 12 additions & 19 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[build-system]
build-backend = "hatchling.build"
requires = ["hatch-fancy-pypi-readme", "hatch-vcs", "hatchling"]
requires = ["hatchling", "uv-dynamic-versioning"]

[project]
authors = [{ name = "Kentaro Wada" }]
Expand All @@ -15,46 +15,39 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Software Development :: Version Control :: Git",
]
dependencies = ["click>=8", "rich>=13"]
description = "Non-interactive, programmatic git hunk staging with stable IDs."
dynamic = ["readme", "version"]
dynamic = ["version"]
keywords = ["automation", "diff", "git", "hunk", "staging", "version-control"]
license = "MIT"
name = "git-hunk"
readme = "README.md"
requires-python = ">=3.10"

[project.urls]
Homepage = "https://github.com/wkentaro/git-hunk"
Issues = "https://github.com/wkentaro/git-hunk/issues"
Repository = "https://github.com/wkentaro/git-hunk"
Homepage = "https://github.com/botify-labs/git-hunk"
Issues = "https://github.com/botify-labs/git-hunk/issues"
Repository = "https://github.com/botify-labs/git-hunk"

[project.scripts]
git-hunk = "git_hunk._cli:cli"

[tool.hatch.metadata.hooks.fancy-pypi-readme]
content-type = "text/markdown"
fragments = [{ path = "README.md" }]

[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
pattern = 'src="(?!https?://)([^"]+)"'
replacement = 'src="https://raw.githubusercontent.com/wkentaro/git-hunk/main/\1"'

[[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]]
pattern = '!\[([^\]]*)\]\((?!https?://)([^)]+)\)'
replacement = '![\1](https://raw.githubusercontent.com/wkentaro/git-hunk/main/\2)'

[tool.hatch.version]
source = "vcs"
source = "uv-dynamic-versioning"

[tool.uv]
exclude-newer = "7 days"

[dependency-groups]
dev = [
"mdformat-frontmatter>=2.0.10",
"mdformat>=0.7",
"pytest-cov>=4",
"pytest-xdist>=3.8.0",
"pytest>=7",
"pytest>=9.0.3",
"ruff>=0.15.9",
"taplo>=0.9",
"ty>=0.0.28",
Expand Down
4 changes: 2 additions & 2 deletions skills/git-hunk/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ description: |
"commit by hunk", or "separate changes into commits".
license: MIT
metadata:
author: wkentaro
author: wkentaro, Botify team
version: 0.1.0
allowed-tools:
- Bash
---

# /git-hunk - split changes into focused commits

Requires: `uv tool install git-hunk` (or `pip install git-hunk`)
Requires: `uv tool install git+https://github.com/botify-labs/git-hunk.git` (or `pip install git-hunk`)

## Workflow

Expand Down
24 changes: 11 additions & 13 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import os
import pathlib
import subprocess
import tempfile
from collections.abc import Generator

import pytest


class GitRepo:
def __init__(self, path: str) -> None:
def __init__(self, path: pathlib.Path) -> None:
self.path = path

def run(
Expand All @@ -27,18 +26,17 @@ def git(self, *args: str) -> str:
return r.stdout

def write_file(self, name: str, content: str) -> str:
filepath = os.path.join(self.path, name)
os.makedirs(os.path.dirname(filepath), exist_ok=True)
filepath = self.path / name
filepath.parent.mkdir(parents=True, exist_ok=True)
with open(filepath, "w") as f:
f.write(content)
return filepath
return str(filepath)


@pytest.fixture
def git_repo() -> Generator[GitRepo]:
with tempfile.TemporaryDirectory() as tmpdir:
repo = GitRepo(tmpdir)
repo.git("init")
repo.git("config", "user.email", "test@test.com")
repo.git("config", "user.name", "Test")
yield repo
def git_repo(tmp_path: pathlib.Path) -> Generator[GitRepo]:
repo = GitRepo(tmp_path)
repo.git("init")
repo.git("config", "user.email", "test@test.com")
repo.git("config", "user.name", "Test")
yield repo
4 changes: 2 additions & 2 deletions tests/e2e/error_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@


def test_not_a_git_repo(tmp_path: Path) -> None:
repo = GitRepo(str(tmp_path))
repo = GitRepo(tmp_path)
cli = GitHunkCLI(repo)
r = cli.run("list")
assert r.returncode != 0
assert "not a git repository" in r.stderr


def test_bare_repo(tmp_path: Path) -> None:
repo = GitRepo(str(tmp_path))
repo = GitRepo(tmp_path)
repo.run("git", "init", "--bare")
cli = GitHunkCLI(repo)
r = cli.run("list")
Expand Down
Loading