git-hunk 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {git_hunk-0.2.0 → git_hunk-0.3.0}/.gitignore +4 -0
- git_hunk-0.3.0/PKG-INFO +380 -0
- git_hunk-0.3.0/README.md +352 -0
- git_hunk-0.3.0/git_hunk/_cli.py +881 -0
- git_hunk-0.3.0/git_hunk/_git.py +201 -0
- git_hunk-0.3.0/git_hunk/_hunk.py +525 -0
- git_hunk-0.3.0/git_hunk/_lines.py +334 -0
- git_hunk-0.3.0/git_hunk/_patch.py +123 -0
- git_hunk-0.3.0/git_hunk/_skills.py +71 -0
- git_hunk-0.3.0/git_hunk/_ui.py +457 -0
- git_hunk-0.3.0/git_hunk/skills/core/SKILL.md +100 -0
- git_hunk-0.3.0/git_hunk/skills/logical-commits/SKILL.md +30 -0
- {git_hunk-0.2.0 → git_hunk-0.3.0}/pyproject.toml +25 -4
- git_hunk-0.2.0/.gitattributes +0 -1
- git_hunk-0.2.0/.github/workflows/lint.yml +0 -13
- git_hunk-0.2.0/.github/workflows/publish.yml +0 -16
- git_hunk-0.2.0/.github/workflows/test.yml +0 -19
- git_hunk-0.2.0/.python-version +0 -1
- git_hunk-0.2.0/Makefile +0 -41
- git_hunk-0.2.0/PKG-INFO +0 -190
- git_hunk-0.2.0/README.md +0 -163
- git_hunk-0.2.0/assets/teaser.png +0 -0
- git_hunk-0.2.0/git_hunk/_cli.py +0 -323
- git_hunk-0.2.0/git_hunk/_git.py +0 -59
- git_hunk-0.2.0/git_hunk/_hunk.py +0 -249
- git_hunk-0.2.0/git_hunk/_lines.py +0 -112
- git_hunk-0.2.0/git_hunk/_patch.py +0 -28
- git_hunk-0.2.0/git_hunk/_ui.py +0 -240
- git_hunk-0.2.0/skills/git-hunk/SKILL.md +0 -62
- git_hunk-0.2.0/taplo.toml +0 -3
- git_hunk-0.2.0/tests/__init__.py +0 -0
- git_hunk-0.2.0/tests/conftest.py +0 -44
- git_hunk-0.2.0/tests/e2e/__init__.py +0 -0
- git_hunk-0.2.0/tests/e2e/conftest.py +0 -46
- git_hunk-0.2.0/tests/e2e/discard_test.py +0 -41
- git_hunk-0.2.0/tests/e2e/error_test.py +0 -72
- git_hunk-0.2.0/tests/e2e/list_test.py +0 -153
- git_hunk-0.2.0/tests/e2e/show_test.py +0 -85
- git_hunk-0.2.0/tests/e2e/stage_test.py +0 -105
- git_hunk-0.2.0/tests/e2e/unstage_test.py +0 -40
- git_hunk-0.2.0/tests/unit/__init__.py +0 -0
- git_hunk-0.2.0/tests/unit/_hunk/__init__.py +0 -0
- git_hunk-0.2.0/tests/unit/_hunk/id_test.py +0 -80
- git_hunk-0.2.0/tests/unit/_hunk/parse_diff_test.py +0 -84
- git_hunk-0.2.0/tests/unit/_hunk/split_test.py +0 -149
- git_hunk-0.2.0/tests/unit/_lines/__init__.py +0 -0
- git_hunk-0.2.0/tests/unit/_lines/filter_test.py +0 -86
- git_hunk-0.2.0/tests/unit/_lines/parse_spec_test.py +0 -59
- git_hunk-0.2.0/tests/unit/_patch_test.py +0 -81
- git_hunk-0.2.0/uv.lock +0 -791
- {git_hunk-0.2.0 → git_hunk-0.3.0}/LICENSE +0 -0
- {git_hunk-0.2.0 → git_hunk-0.3.0}/git_hunk/__init__.py +0 -0
- {git_hunk-0.2.0 → git_hunk-0.3.0}/git_hunk/__main__.py +0 -0
git_hunk-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: git-hunk
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Non-interactive, programmatic git hunk staging with durable Hunk IDs.
|
|
5
|
+
Project-URL: Homepage, https://github.com/wkentaro/git-hunk
|
|
6
|
+
Project-URL: Issues, https://github.com/wkentaro/git-hunk/issues
|
|
7
|
+
Project-URL: Repository, https://github.com/wkentaro/git-hunk
|
|
8
|
+
Author: Kentaro Wada
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: automation,diff,git,hunk,staging,version-control
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: click>=8
|
|
26
|
+
Requires-Dist: rich>=13
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# git-hunk
|
|
30
|
+
|
|
31
|
+
[](https://pypi.org/project/git-hunk/)
|
|
32
|
+
[](https://pypi.org/project/git-hunk/)
|
|
33
|
+
[](https://pypi.org/project/git-hunk/)
|
|
34
|
+
[](https://github.com/wkentaro/git-hunk/actions/workflows/test.yml)
|
|
35
|
+
|
|
36
|
+
Non-interactive, programmatic alternative to `git add -p`.
|
|
37
|
+
|
|
38
|
+
Every staged or unstaged Hunk gets a durable ID so you can inspect, filter, and
|
|
39
|
+
stage changes without interactive prompts. Duplicate Hunks get unique
|
|
40
|
+
Conditional IDs.
|
|
41
|
+
|
|
42
|
+
<img src="https://raw.githubusercontent.com/wkentaro/git-hunk/main/assets/teaser.png" alt="git-hunk teaser" width="800">
|
|
43
|
+
|
|
44
|
+
## Why?
|
|
45
|
+
|
|
46
|
+
`git add -p` requires interactive input. That makes it unusable for:
|
|
47
|
+
|
|
48
|
+
- **AI agents** (Claude Code, Codex, etc.) that need to split changes into logical commits
|
|
49
|
+
- **Scripts & CI/CD** that automate commit organization
|
|
50
|
+
- **Editor integrations** that want hunk-level staging without shelling out to a TUI
|
|
51
|
+
|
|
52
|
+
`git-hunk` solves this by assigning each staged or unstaged Hunk a durable ID
|
|
53
|
+
and exposing simple stage/unstage/discard commands.
|
|
54
|
+
|
|
55
|
+
## Eval
|
|
56
|
+
|
|
57
|
+
One agent (Claude Code 2.1.226, `claude-sonnet-5`, reasoning effort `high`)
|
|
58
|
+
attempted the same eight tasks from identical repository state, three times per
|
|
59
|
+
variant: organize a dirty working tree into correct, focused commits, once
|
|
60
|
+
following git-hunk's bundled skills and once restricted to bare Git. The
|
|
61
|
+
[checked-in eval harness](https://github.com/wkentaro/git-hunk/tree/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval)
|
|
62
|
+
grades the exact resulting repository state — commit partition and order, final
|
|
63
|
+
tree, index, and leftovers. This table records the qualifying run; `make eval`
|
|
64
|
+
reruns the protocol and prints a table in the same format.
|
|
65
|
+
|
|
66
|
+
| Task | git-hunk | bare Git |
|
|
67
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | ----------------------------------------------- |
|
|
68
|
+
| [split_refactor_vs_feature](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/split_refactor_vs_feature.py) | PASS 3/3 · 3c · 4t | PASS 3/3 · 3c [2-3] · 4t [3-4] |
|
|
69
|
+
| [separate_mixed_hunks](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/separate_mixed_hunks.py) | PASS 3/3 · 3c [3-4] · 4t [4-5] | MIXED 1/3 partition · 15c [11-20] · 16t [12-21] |
|
|
70
|
+
| [drop_debug_lines](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/drop_debug_lines.py) | PASS 3/3 · 3c · 4t | MIXED 2/3 partition · 16c [8-19] · 17t [9-20] |
|
|
71
|
+
| [protect_unrelated_work](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/protect_unrelated_work.py) | PASS 3/3 · 3c · 4t | PASS 3/3 · 2c [2-3] · 3t [3-4] |
|
|
72
|
+
| [split_single_hunk](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/split_single_hunk.py) | PASS 3/3 · 5c [3-5] · 6t [4-6] | PASS 3/3 · 14c [9-15] · 15t [10-16] |
|
|
73
|
+
| [separate_formatter_noise](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/separate_formatter_noise.py) | PASS 3/3 · 4c [3-4] · 5t [4-5] | PASS 3/3 · 16c [9-30] · 17t [10-31] |
|
|
74
|
+
| [pick_duplicate_hunk](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/pick_duplicate_hunk.py) | PASS 3/3 · 3c · 4t | PASS 3/3 · 10c [9-17] · 11t [10-18] |
|
|
75
|
+
| [commit_parseable_subset](https://github.com/wkentaro/git-hunk/blob/9829b7ac6fd871cd874d47e7424c9082bd58dbaf/eval/tasks/commit_parseable_subset.py) | PASS 3/3 · 5c [3-6] · 6t [4-7] | PASS 3/3 · 14c [12-15] · 15t [13-16] |
|
|
76
|
+
| **total** | **8/8 · 29c [24-31] · 37t [32-39]** | **6/8 (2 mixed) · 90c [62-122] · 98t [70-130]** |
|
|
77
|
+
|
|
78
|
+
`c` = tool calls, `t` = turns; a cell reports the median of its three repeats
|
|
79
|
+
with the observed range in brackets, dropped where every repeat agreed, and the
|
|
80
|
+
pass column counts passing repeats. `MIXED j/3` means the variant passed j of its
|
|
81
|
+
three repeats, and `partition` names the failure: the commits made do not match
|
|
82
|
+
the required change groups. The cost column is omitted: bare Git runs second in
|
|
83
|
+
each pair and partly reads the prompt cache the git-hunk run warmed, so raw costs
|
|
84
|
+
are not order-neutral
|
|
85
|
+
([#224](https://github.com/wkentaro/git-hunk/issues/224)). Three samples per task
|
|
86
|
+
variant, dated 2026-08-09 at commit `9829b7a`.
|
|
87
|
+
|
|
88
|
+
## Install
|
|
89
|
+
|
|
90
|
+
Requires Git 2.28 or later. `git-hunk` forces canonical diff paths with
|
|
91
|
+
`git diff --no-relative`, which earlier versions of Git do not accept.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
pip install git-hunk
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
uv tool install git-hunk
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Verify it works:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
git-hunk --version
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
> [!TIP]
|
|
110
|
+
> To try the latest development version (the head of `main` on GitHub) before
|
|
111
|
+
> it is published:
|
|
112
|
+
>
|
|
113
|
+
> ```bash
|
|
114
|
+
> uv tool install git+https://github.com/wkentaro/git-hunk
|
|
115
|
+
> ```
|
|
116
|
+
|
|
117
|
+
### For AI agents
|
|
118
|
+
|
|
119
|
+
A usage guide ships inside the CLI, so agents (Claude Code, Codex, etc.) can
|
|
120
|
+
load it on demand. It always matches the installed version, so it never goes
|
|
121
|
+
stale:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
git-hunk skills get core
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`core` covers the tool itself. A separate `logical-commits` skill covers how to
|
|
128
|
+
group hunks into commits and order them; it is optional, so a project that
|
|
129
|
+
already defines its own commit conventions can load `core` alone:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
git-hunk skills # list available skills
|
|
133
|
+
git-hunk skills get core logical-commits # load both
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`git-hunk --help` points here first.
|
|
137
|
+
|
|
138
|
+
## Quick start
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
# See all hunks across staged, unstaged, and untracked files
|
|
142
|
+
git-hunk list
|
|
143
|
+
|
|
144
|
+
# Show the diff for a specific hunk
|
|
145
|
+
git-hunk show d161935
|
|
146
|
+
|
|
147
|
+
# Stage specific hunks, then commit
|
|
148
|
+
git-hunk stage d161935 a3f82c1
|
|
149
|
+
git commit -m "feat: add validation for user input"
|
|
150
|
+
|
|
151
|
+
# Stage the remaining hunks
|
|
152
|
+
git-hunk stage e7b4012
|
|
153
|
+
git commit -m "fix: handle empty response in API client"
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Usage
|
|
157
|
+
|
|
158
|
+
### Repository paths
|
|
159
|
+
|
|
160
|
+
A Repository path is relative to the worktree root, uses `/`, and has the same
|
|
161
|
+
meaning from every invocation directory. Every path in output and every file
|
|
162
|
+
operand for `list`, `stage`, `unstage`, `discard`, and `commit` is a Repository
|
|
163
|
+
path. A leading `./` and internal `..` components are normalized. Absolute paths
|
|
164
|
+
and paths that escape the worktree are rejected.
|
|
165
|
+
|
|
166
|
+
File operands select one exact changed file. Directories, globs, and Git pathspec
|
|
167
|
+
syntax are not expanded. Quote operands that contain shell metacharacters so the
|
|
168
|
+
shell passes them unchanged. For example, from `sub/`, `same.txt` selects the
|
|
169
|
+
file at the worktree root, while `sub/same.txt` selects the file inside `sub/`.
|
|
170
|
+
`show` remains ID-only.
|
|
171
|
+
|
|
172
|
+
### Unsupported repository states
|
|
173
|
+
|
|
174
|
+
git-hunk rejects detected rename, copy, and unmerged index states before it
|
|
175
|
+
writes inventory output or changes the repository. This prevents partial JSON,
|
|
176
|
+
partial inventory, false clean results, and partial mutation. Resolve an
|
|
177
|
+
unmerged index with Git before retrying. Full rename and copy support is not yet
|
|
178
|
+
available; it remains tracked in [#53](https://github.com/wkentaro/git-hunk/issues/53).
|
|
179
|
+
|
|
180
|
+
### Hunk IDs
|
|
181
|
+
|
|
182
|
+
A canonical Hunk ID is a full SHA-256 value. JSON returns it in full. Human
|
|
183
|
+
output shows the shortest unambiguous prefix of at least seven characters, and
|
|
184
|
+
commands accept unambiguous prefixes without case sensitivity. IDs are
|
|
185
|
+
calculated from the combined staged and unstaged inventory, including when a
|
|
186
|
+
status filter shows only one side.
|
|
187
|
+
|
|
188
|
+
An Unchanged Hunk keeps its ID when it moves completely between staged and
|
|
189
|
+
unstaged state or when other complete Hunks move. A partial-line operation
|
|
190
|
+
creates new Hunks with new IDs.
|
|
191
|
+
|
|
192
|
+
Hunks with the same Repository path and patch content form a Duplicate Hunk
|
|
193
|
+
group. Each member gets a unique Conditional Hunk ID, shown with a
|
|
194
|
+
`conditional` label in human output and `"id_stability": "conditional"` in JSON.
|
|
195
|
+
The ID can change when its Duplicate Hunk group changes. After a partial-line
|
|
196
|
+
operation or an operation on a Conditional Hunk ID, address anything remaining
|
|
197
|
+
by Repository path, which is ID-independent, or run `git-hunk list` again for
|
|
198
|
+
the new IDs.
|
|
199
|
+
|
|
200
|
+
### List hunks
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
git-hunk list # all hunks (unstaged + staged + untracked)
|
|
204
|
+
git-hunk list --unstaged # unstaged hunks only
|
|
205
|
+
git-hunk list --staged # staged hunks only
|
|
206
|
+
git-hunk list src/foo.py src/bar.py # specific files
|
|
207
|
+
git-hunk list --json # JSON output for scripting
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Show hunks
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
git-hunk show # show all hunks (staged + unstaged)
|
|
214
|
+
git-hunk show d161935 # show a single hunk
|
|
215
|
+
git-hunk show d161935 a3f82c1 # show multiple hunks
|
|
216
|
+
git-hunk show --staged # show all staged hunks
|
|
217
|
+
git-hunk show --unstaged # show all unstaged hunks
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Stage, unstage, discard
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
git-hunk stage d161935 # stage a hunk
|
|
224
|
+
git-hunk stage d161935 a3f82c1 # stage multiple hunks
|
|
225
|
+
git-hunk stage d161935 -l 3,5-7 # stage specific lines only
|
|
226
|
+
git-hunk stage d161935 --exclude-matching debug # stage all but lines containing "debug"
|
|
227
|
+
git-hunk stage d161935 --include-matching xfail # stage only lines containing "xfail"
|
|
228
|
+
git-hunk unstage d161935 # move back to working tree
|
|
229
|
+
git-hunk unstage d161935 -l 3,5-7 # unstage specific lines only
|
|
230
|
+
git-hunk discard d161935 # restore from the index
|
|
231
|
+
git-hunk discard d161935 -l ^3,^5-7 # discard excluding specific lines
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
`--include-matching` / `--exclude-matching` select changed lines by content
|
|
235
|
+
instead of line number (literal substring by default, `--regex` for regular
|
|
236
|
+
expressions). Both are repeatable and OR'd, case-sensitive, and error if nothing
|
|
237
|
+
matches. They are mutually exclusive with `-l` and with each other.
|
|
238
|
+
|
|
239
|
+
Line selection accepts any subset of a pure addition or pure deletion. Selecting
|
|
240
|
+
one side of a one-for-one replacement is rejected, because it would leave a
|
|
241
|
+
deletion-only or addition-only half; select both lines, match text they share,
|
|
242
|
+
or pass `--allow-one-sided` when that half is what you want. A grouped
|
|
243
|
+
replacement with multiple deleted or added lines must be selected as a whole or
|
|
244
|
+
not selected, and `--allow-one-sided` does not relax that. Numeric range
|
|
245
|
+
endpoints are checked against the Hunk before expansion, and no-newline state is
|
|
246
|
+
preserved for each patch side. Submodule pointer changes and whole-file Hunks do
|
|
247
|
+
not support line selection. Select the Hunk as a whole.
|
|
248
|
+
|
|
249
|
+
A binary, mode-only, type, or empty tracked file change is a whole-file Hunk.
|
|
250
|
+
Plain output labels empty tracked changes as `Empty file (added)` or
|
|
251
|
+
`Empty file (deleted)`. When one file has a mode change and text edits, the mode
|
|
252
|
+
and each text range are separate Hunks. Selecting text does not apply the mode
|
|
253
|
+
change, and selecting the mode Hunk does not apply text.
|
|
254
|
+
|
|
255
|
+
### Commit
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
git-hunk commit d161935 -m "fix: ..." # stage a hunk and commit it in one step
|
|
259
|
+
git-hunk commit d161935 -l 3,5-7 -m "..." # stage specific lines and commit
|
|
260
|
+
git-hunk commit d161935 --exclude-matching debug -m "..." # commit all but matching lines
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
`commit` aborts if anything is already staged, so the commit contains exactly
|
|
264
|
+
the selected hunks. It accepts the same `-l`, `--include-matching`,
|
|
265
|
+
`--exclude-matching`, `--regex`, and `--allow-one-sided` selection options as
|
|
266
|
+
`stage`.
|
|
267
|
+
|
|
268
|
+
### JSON output
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
git-hunk list --json # inventory: every hunk, no body
|
|
272
|
+
git-hunk show <id> --json # the same hunks plus a structured per-line body
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Both emit a versioned envelope (`schema_version` is currently `2`) so consumers
|
|
276
|
+
can depend on a stable shape. `list --json` is a lean inventory and carries no
|
|
277
|
+
body; `show --json` adds a structured `lines` array. A `show --json` hunk
|
|
278
|
+
(`list --json` is identical but without the `lines` field):
|
|
279
|
+
|
|
280
|
+
```json
|
|
281
|
+
{
|
|
282
|
+
"schema_version": 2,
|
|
283
|
+
"hunks": [
|
|
284
|
+
{
|
|
285
|
+
"id": "d161935000000000000000000000000000000000000000000000000000000000",
|
|
286
|
+
"id_stability": "stable",
|
|
287
|
+
"file": { "text": "src/main.py" },
|
|
288
|
+
"status": "unstaged",
|
|
289
|
+
"change_kind": "M",
|
|
290
|
+
"a_mode": "100644",
|
|
291
|
+
"b_mode": "100644",
|
|
292
|
+
"binary": false,
|
|
293
|
+
"header": "@@ -10,3 +10,5 @@",
|
|
294
|
+
"context_before": { "text": "def main():" },
|
|
295
|
+
"additions": 2,
|
|
296
|
+
"deletions": 0,
|
|
297
|
+
"lines": [
|
|
298
|
+
{ "n": 1, "op": " ", "content": { "text": " x = 1" } },
|
|
299
|
+
{ "n": 2, "op": "+", "content": { "text": " y = 2" } }
|
|
300
|
+
]
|
|
301
|
+
}
|
|
302
|
+
]
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
| Field | Type | Description |
|
|
307
|
+
| ---------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
308
|
+
| `schema_version` | int | Envelope version; bumped on any incompatible change to the shape below. |
|
|
309
|
+
| `hunks` | array | The hunks (empty array when there are no changes). |
|
|
310
|
+
| `id` | string | Full canonical SHA-256 Hunk ID; empty for an `untracked` entry, which no command can address. Human output uses a unique prefix of at least seven characters. |
|
|
311
|
+
| `id_stability` | string | `stable` or `conditional`. An untracked inventory entry reports `stable`, but its empty `id` remains unaddressable. |
|
|
312
|
+
| `file` | union | Repository path of the changed file, as a byte-safe `{text\|bytes}` union (see below). |
|
|
313
|
+
| `status` | string | One of `staged`, `unstaged`, `untracked`. |
|
|
314
|
+
| `change_kind` | string | Git status letter: `A` added, `D` deleted, `M` modified, `T` typechange (`R`/`C` reserved and currently rejected). Always present. |
|
|
315
|
+
| `a_mode` | string \| null | 6-digit octal git mode on the pre-image side; `null` when that side does not exist. |
|
|
316
|
+
| `b_mode` | string \| null | 6-digit octal git mode on the post-image side; `null` when that side does not exist. |
|
|
317
|
+
| `binary` | bool | Whether the change is binary. Always present. |
|
|
318
|
+
| `header` | string \| null | The bare `@@ -a,b +c,d @@` range for a text hunk; `null` for a whole-file hunk (binary, mode-only, type, or empty tracked file change) or an `untracked` inventory entry. |
|
|
319
|
+
| `context_before` | union \| null | The function/section name after a text hunk's `@@` header, as a `{text\|bytes}` union; `null` for a text hunk without a heading, a whole-file hunk, or an `untracked` inventory entry. |
|
|
320
|
+
| `additions` | int | Number of added lines. |
|
|
321
|
+
| `deletions` | int | Number of removed lines. |
|
|
322
|
+
| `lines` | array | `show --json` only. The structured body; `[]` for a whole-file hunk. See below. |
|
|
323
|
+
|
|
324
|
+
A `lines` entry is `{ "n", "op", "content", "no_newline"? }`:
|
|
325
|
+
|
|
326
|
+
| Field | Type | Description |
|
|
327
|
+
| ------------ | ------ | --------------------------------------------------------------------------------------------------- |
|
|
328
|
+
| `n` | int | 1-based position within the hunk body — the index `-l` line selection uses. Counts every body line. |
|
|
329
|
+
| `op` | string | `" "` context, `"+"` addition, `"-"` deletion. |
|
|
330
|
+
| `content` | union | The line text **without** its leading op character, as a `{text\|bytes}` union. |
|
|
331
|
+
| `no_newline` | bool | Present and `true` only when the line has no trailing newline; consumes no `n`. |
|
|
332
|
+
|
|
333
|
+
Any field carrying arbitrary git/source bytes (`file`, `context_before`,
|
|
334
|
+
`lines[].content`) is a byte-safe `{text | bytes}` union: `{"text": "..."}` for
|
|
335
|
+
valid UTF-8, else `{"bytes": "<base64>"}`. It is always an object, so consumers
|
|
336
|
+
have one code path and strict JSON parsers never see a lone surrogate.
|
|
337
|
+
|
|
338
|
+
Adding a new field is backward-compatible and does not change `schema_version`;
|
|
339
|
+
renaming, removing, or changing the type of an existing field bumps it. (Before
|
|
340
|
+
`schema_version` existed, `list --json` returned a bare array.)
|
|
341
|
+
|
|
342
|
+
## Comparison
|
|
343
|
+
|
|
344
|
+
| | Interactive | Programmatic | Hunk IDs | Line-level control | JSON output |
|
|
345
|
+
| ---------------- | ----------- | ------------ | -------- | ------------------ | ----------- |
|
|
346
|
+
| `git add -p` | Yes | No | No | Yes | No |
|
|
347
|
+
| `git add <file>` | No | Yes | No | No | No |
|
|
348
|
+
| **`git-hunk`** | **No** | **Yes** | **Yes** | **Yes** | **Yes** |
|
|
349
|
+
|
|
350
|
+
## How it works
|
|
351
|
+
|
|
352
|
+
1. Rejects detected rename, copy, and unmerged states.
|
|
353
|
+
2. Parses staged and unstaged `git diff` output into one combined Hunk inventory.
|
|
354
|
+
3. Assigns each Hunk a full canonical SHA-256 ID and a unique human prefix.
|
|
355
|
+
4. Gives members of a Duplicate Hunk group unique Conditional Hunk IDs.
|
|
356
|
+
5. For staging, reconstructs a minimal patch and pipes it through `git apply --cached`.
|
|
357
|
+
6. For discarding, reconstructs a reverse patch and applies it to the working tree.
|
|
358
|
+
|
|
359
|
+
Text IDs use the Repository path and patch body, including context and newline
|
|
360
|
+
state. They exclude `@@` ranges, section headings, and staged state. Whole-file
|
|
361
|
+
IDs include the actual binary, mode, or type change. This keeps an Unchanged
|
|
362
|
+
Hunk stable while complete Hunks move. A partial operation changes the patch
|
|
363
|
+
content and creates new IDs.
|
|
364
|
+
|
|
365
|
+
## Contributing
|
|
366
|
+
|
|
367
|
+
Bug reports, feature requests, and pull requests are welcome on
|
|
368
|
+
[GitHub](https://github.com/wkentaro/git-hunk).
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
git clone https://github.com/wkentaro/git-hunk.git
|
|
372
|
+
cd git-hunk
|
|
373
|
+
make setup # install dependencies
|
|
374
|
+
make test # run tests
|
|
375
|
+
make lint # run linters
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## License
|
|
379
|
+
|
|
380
|
+
MIT ([LICENSE](https://github.com/wkentaro/git-hunk/blob/main/LICENSE))
|