Skip to content

Releases: shhac/git-hunk

v0.20.0

Choose a tag to compare

@github-actions github-actions released this 25 Sep 11:33
8bab0d3

Added

  • git hunk stash pop merges a stash back into files that have other unstaged changes, which git stash pop refuses ("Your local changes to the following files would be overwritten by merge"). That refusal met every stash of some of a file's hunks and not others, and every file edited again after stashing. The entry's worktree changes are merged file by file, the index is left as it is, and a conflict is left as git leaves one: markers labelled Updated upstream and Stashed changes, unmerged index entries, CONFLICT (content): Merge conflict in <path> and The stash entry is kept in case you need it again., exit 1. Where git's pop succeeds it still does the work, so the result is git's own. An untracked file in the way now refuses the pop before anything is restored, where git's pop merged the tracked changes first; and a conflict in git's own pop is now reported, where only the exit status said so before.

Fixed

  • An untracked symlink's diff, which git-hunk writes itself, named the path bare where git would C-quote it (a TAB, a quote, a backslash or a control character, and under core.quotePath a non-ASCII byte), and left off the TAB git ends a name containing a space with. A symlink with a TAB in its name could not be added (bad git-diff - inconsistent new filename), and diff showed a header git never writes. The header is now git's own, byte for byte.

  • Adding a file's deletion together with the untracked file it was moved to stages a rename, but add reported the deletion as → ?, a hash list --staged never shows. Both are now reported on one line under the rename's hash: staged 7c745cd aa914db → b34f350 new-name.txt.

  • A git failure while stash was writing the stash tree or the untracked-files commit, or while restore --3way was recording a conflict, exited on the spot and left a git-hunk-* temp index behind in the temp directory. Those failures now unwind, so the temp index is removed; the message and exit status are unchanged.

  • On an unborn branch (no commits yet), commit failed with git's fatal: Not a valid object name HEAD, and stash with a raw git rev-parse failure. commit now makes the first commit, as git commit does; commit --amend says error: you have nothing to amend, and stash says error: you do not have the initial commit yet, in git's words, changing nothing. list --staged, count --staged and reset already worked there, against the empty tree, as git diff --cached and git reset do.

  • A typechange between a binary and a symlink (a binary replaced by a symlink, or the reverse) could not be added, reset, committed, stashed or restored: error: <path>: already exists in index (or in working directory). The binary half is staged, unstaged or restored by path, which takes the whole path, and the symlink half was applied as a patch beside it, before the binary half had made room or after it was already done. When both halves are selected, the symlink half now goes with the binary half.

  • stash of a hunk in an intent-to-add entry (git add -N), or in a rename whose new side is one, took the file out of the worktree and left the entry naming it, so it showed as deleted. stash now refuses while the index holds any intent-to-add entry, as git stash does, and nothing changes: error: cannot stash while '<path>' is intent-to-add, exit 1. That includes stashing other files: a stash made beside such an entry cannot be popped while the entry stands.

  • A line spec on a new or deleted file failed in most directions. The patch header was fixed when the diff was read, so the filtered patch still claimed to create or delete the whole file while its body kept the deselected lines, and git refused it: reset <sha>:2 on a staged new file, restore --force <sha>:2 on an untracked file, and add/commit of part of a deletion all failed ("new file depends on old contents", "patch did not apply cleanly"). Headers are now rendered from what the line spec leaves: the deselected lines stay where they were, so a partial undo of a new file or a partial deletion is an edit to a file that still exists. The index line is kept, so --3way works on a partial selection too.

  • A line spec on an empty file (add <empty>:1) silently applied the whole file. It is now rejected, as are line specs on a symlink (its one line is its whole target) and on either half of a typechange: error: line selection not supported for empty file|symlink|typechange '<path>', matching the existing message for binary files.

  • Every change to a binary path had the same hash (bin.dat hashed alike as a worktree edit and as a new file under --ref), so a hash listed before the file changed again still matched it and check could not notice. A binary hash now includes the blob ids from its index line. Binary hashes change with this release, and differ between SHA-1 and SHA-256 repositories.

  • An empty new or deleted file's patch carried --- /dev/null / +++ b/<path> lines git never writes for one, with the path unquoted even where git would quote it. The header is now git's own: diff shows it that way, and git apply takes it as is.

  • An untracked binary file's hash depended on core.abbrev: untracked files were diffed without --full-index, so the blob ids its hash is taken over came abbreviated. They are now full, as for tracked files, and an untracked file's patch (a symlink's too) carries full ids in its index line.

  • A file named like a revision broke the commands that name one: with a file called main, list --staged --ref main failed with git's "ambiguous argument 'main': both revision and filename", and a file called HEAD made every stash fail the same way. Revisions now always end with --, so git never has to guess.

  • The result hash add and reset print for a rename was not one list would show: adding a rename reported it as a new file under a hash that list --staged never lists, and resetting one printed → ?, as did resetting a staged new file. The result is now read back from a diff that covers both paths of a rename and, for reset, the untracked file it leaves: adding a rename reports the rename's list --staged hash, and resetting one reports the new path, now untracked, and the old path's deletion (unstaged b34f350 → 7c745cd,aa914db new-name.txt).

  • A stash entry recorded the stashed hunks on top of HEAD as both its tree and its index commit, so any staged change made git stash pop --index fail ("Index was not unstashed") and left the staged file unstaged; with nothing staged, pop --index staged the stashed hunks. An entry now has the same shape as git stash push --keep-index -- <paths> would give it: its index commit is the index as it stands and its tree is that index plus the stashed hunks. Every git stash command treats it as it would the native entry: pop --index and apply --index restore the stashed hunks unstaged and keep the index, and git stash show lists staged changes as well as stashed ones, as it does for a native keep-index entry. A stash while the index has unmerged paths is refused (error: cannot stash while the index has unmerged paths), as git stash refuses it, rather than recording an index commit that was not the index.

  • --ref nope failed with git's complaint about the empty tree it had been expanded against. Every revision --ref names, including each side of a range, is now checked before anything runs: error: bad revision 'nope'.

  • A path containing a space kept the TAB git ends such a name with on its ---/+++ lines, so list --file "a b.txt" found nothing, list --porcelain printed an extra empty column, add/reset printed → ?, and a rename to or from such a path reported a result hash list never shows. The TAB is now dropped as git defines it: only after a name containing a space. Hashes of hunks in paths with spaces change with this release.

  • Under diff.renames=copies, a copied file's copy from/copy to lines were dropped, so git applied the section as a rename: reset of the copy's hunk took the source out of the index along with its own staged change, and add --ref of a copy commit staged a rename. Copies now stay copies. Taking a copy's hunk back out (reset, restore, stash) edits the copy alone, leaving it a copy of its source, since git apply --reverse would otherwise write the copy back over the source. A copy with no content change is named by -v like a rename with none.

  • An untracked file whose name git C-quotes (ünï.txt, or one with a quote or a tab in it) was invisible: list did not show it, so it could not be added, and resetting a staged new file with such a name printed → ?. Untracked files are now listed NUL-separated, as git names them. The listing is also scoped to the paths in question, so add/reset no longer list every untracked file in the repository twice to report their results.

  • apply.whitespace=error made add, stash, commit and every other command that applies a patch reject a hunk with trailing whitespace, and apply.whitespace=fix would have stripped it on the way. The content is already in the worktree or history; git-hunk now moves it as it is, as git add does.

  • A stash whose stashed changes could not be taken back out of the worktree printed a warning and exited 0, with the changes both stored and still in the worktree. It is now an error, exit 1: error: cannot remove the stashed changes from the worktree, with hints that the changes are saved in stash@{0} and still in the worktree, and that git stash drop keeps working on them. A tracked binary that could not be checked out, or an untracked file that could not be deleted, counts the same.

  • restore --3way handed git apply a --3way that implies --index. Without --ref it always failed ("does not match index", since the hunk is exactly where worktree and index differ), and a partial restore of a deleted file wrote...

Read more

v0.19.0

Choose a tag to compare

@github-actions github-actions released this 23 Sep 22:05
3bef377

Added

  • Git 3.0 readiness: git-hunk works in SHA-256 and reftable repositories, the defaults Git 3.0 gives every new repository, as well as in SHA-1 ones, with nothing to configure. Wherever it needs an object ID it asks git, which answers in the repository's own format. The integration suite gains a git3-defaults hostile profile (SHA-256, reftable, main, safe.bareRepository=explicit) that CI runs on every push; it refuses a git too old to honour those settings rather than passing vacuously.

Fixed

  • --ref <initial-commit> failed in a SHA-256 repository with "unknown revision". It diffed against the SHA-1 empty tree, which a SHA-256 repository does not have.
  • An untracked symlink's diff named its blob with a SHA-1 hash even in a SHA-256 repository.
  • An uppercase hunk-hash prefix passed validation and then matched nothing ("no hunk matching"). It is now rejected by name ("hunk hashes are lowercase hex"), in line with Git 3.0's lowercase-only object IDs.
  • commit --amend dropped the original commit's changes. The temp index was seeded from HEAD~1 while the hunks are relative to HEAD, so the amended commit held only the new hunk and the original change reappeared as staged. On a root commit --amend failed outright. Both now build on HEAD, as git commit --amend does.
  • commit --dry-run checked against the real index while the commit builds on HEAD, so a staged edit next to the target hunk made the preview pass and the commit fail.
  • A file added by a pre-commit hook with a non-ASCII name was left behind as a staged deletion, which the next plain git commit would record. Path listings are now NUL-separated, and every file of a multi-file commit counts as a target (previously only the first file of each patch did).
  • Binary renames: a.bin → b.bin was listed and reset under the old path, leaving the rename staged, and a rename between names of different lengths was invisible to list, count and -v. Renames now take their path from rename to. A C-quoted path ending in a backslash was dropped by the same parser.
  • add/reset with a --file that matched no hunk exited 0 having done nothing. They now report "no hunks matching file" and exit 1, like the other commands.
  • add, reset and restore --dry-run rejected every file↔symlink typechange ("already exists" / "wrong type") that the real operation applies cleanly. The dry run now checks the delete and create halves as one sequence.
  • reset --ref <X> could not succeed: git received diff --cached X^..X and printed its usage text. It now takes hunks from the same diff as list --ref X and removes them from the index, the mirror of add --ref X.

Changed

  • Internal restructure with no change in behaviour, checked against the previous binary across a few hundred scenarios:
    • The per-command option structs share one Common struct, replacing a field-copy layer driven by reflection. Whether a command accepts --3way now comes from the command spec.
    • Subcommands dispatch through one typed path.
    • Commands load and select hunks through one shared path.
    • Patches are built in the order they apply.
    • One numbered iterator walks every hunk body, so diff -n numbering and line-spec selection cannot drift apart.
    • diff.zig parses one file section at a time, shared with the skipped-path report.
    • Colour, hash and summary rendering live in format.zig.
    • Stash machinery moves into stash.zig, and new head_match.zig and check.zig hold the index→HEAD matcher and check's resolver.
  • Test suites use the harness first_sha helper throughout, and the symlink and typechange cases move to tests/test_symlink.sh.

v0.18.1

Choose a tag to compare

@github-actions github-actions released this 20 Aug 20:42
4522bc2

Fixed

  • git hunk list now names an unknown flag (error: unknown flag '--badflag') instead of printing the whole usage banner. parseListArgs returned a bare error where every other parser routes through the shared unknownFlag helper, so list was the only command that never told you which flag was wrong.
  • zig build test ran 175 of 391 unit tests. A module's tests are discovered only if main.zig's comptime block imports it, and args.zig (175 tests, the whole CLI argument parser) and commands.zig (41) were absent — so their tests were skipped silently rather than reported. Two had rotted undetected in the meantime: one referenced a struct field that had been renamed and no longer compiled, and one freed string literals and aborted. Both fixed; all 391 now run.

Changed

  • commands.zig is split from 2385 lines into three files. The result-group machinery — mapping applied hunks onto their post-apply result hashes, which is pure set arithmetic over two hunk snapshots — moves to result_groups.zig, and the temp-index commit transaction moves to commit.zig, each taking its own tests. cmdCommit stays alongside the other subcommand entry points so all nine still dispatch through one surface. No behaviour or interface change.
  • Test suites share first_sha, bytes_of, blob_bytes and want_bytes from harness.sh rather than redefining them per file.

v0.18.0

Choose a tag to compare

@github-actions github-actions released this 20 Aug 19:43
15a1a12

Fixed

  • A configured external diff driver no longer makes a dirty tree look clean. With diff.external set, or GIT_EXTERNAL_DIFF exported, git hands the diff to that program and writes nothing to stdout while exiting 0 — so list showed no hunks, count returned 0, and check passed vacuously. Not a crash and not a parse error: a wrong answer shaped exactly like a clean tree, which an agent has no way to distinguish. Every git diff call site now passes --no-ext-diff (which defeats both the config key and the environment variable), including the --no-index path used for untracked files.
  • A diff.<driver>.textconv filter no longer leaks converted text into the hunk listing. Hunks on such a path showed the driver's rendering of the blob rather than its real content, and the patch rebuilt from that rendering was rejected by git apply ("patch does not apply") — add, reset, restore and commit all failed on those paths. Diff calls now pass --no-textconv, so textconv paths behave like any other.
  • Redirecting stdout to a regular file no longer overwrites what preceded it. The stdout writer was positional and started at offset 0, ignoring the offset the shell had already put on the descriptor, so git hunk list >> log truncated the log and { echo header; git hunk list; } > out overwrote the header. Output is now streamed.

Added

  • list --verbose and count --verbose now name changed paths that produced no hunk, with the reason and the git add <path> that stages them. A submodule pointer bump, a mode change and a rename with no content change are all deliberate skips — they have no line-level content for a hash to address — but the skip was silent, so a tree whose only change was one of them read here as having nothing to stage. A mode change is reported even when the same file also has content hunks, since the mode is unstageable either way. Derived from the diff text the parser consumed rather than a parallel copy of the skip rules, so a future skip cannot go unreported.

Changed

  • git hunk commit builds its temporary index under TMPDIR when set, rather than always under /tmp — the same rule the shell and mktemp follow, so a caller that has isolated its temp directory keeps that isolation.
  • The man page now carries the version in its .TH line, so man git-hunk names the release it documents and a packaging step that shipped a stale copy leaves a trace. zig build docs must therefore be re-run on a version bump; CI's existing drift check enforces it.
  • Test coverage for classes that were previously unpinned: the \ No newline at end of file marker end to end (all four positions across add, reset, restore, commit, stash, and line-spec selection, asserting file and blob bytes); non-UTF-8 file content as distinct from filenames; line-ending normalisation under core.autocrlf true/input, core.eol with text=auto, and none — each pinned against git's own answer (git add, git checkout --) rather than against the pre-edit bytes; submodule and mode-change skips; an inherited GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE (which hooks, rebases and CI all set); and stdout redirected to a regular file.
  • Releases are now packaged by tools/package-release.sh, which CI runs too, and the resulting tarball is read back by tests/check-release-artifact.sh — the binary, the man page and all four completion files must be present, the binary must run, and the shipped man page and completions must cover every command the shipped binary lists. Previously nothing verified a release's contents, so a workflow edit could drop the man page or a completion file silently.
  • The whole integration suite can be re-run under hostile git configuration: tests/run-hostile.sh applies a profile through GIT_CONFIG_GLOBAL (so it reaches every git invocation in every test, including the ones git-hunk spawns) and requires the same assertion count as a default-config run — a profile that silently emptied every diff would otherwise pass by asserting nothing. Eight profiles cover external diff drivers, textconv, path prefixes and diff.relative, color.ui and color.diff separately, diff algorithm and blank-line suppression, core.quotePath, and pagers with an order file. CI runs one job per profile, with the matrix generated from the profile list so adding a profile adds a job. Nothing previously set a hostile config anywhere, so every flag the tool passes to defend itself was unverified.
  • The test harness no longer sets pipefail. The suite is built on echo "$OUT" | grep -q X, where grep -q exits on its first match and echo then dies of SIGPIPE with status 141; under pipefail that 141 became the pipeline's status, so a passing assertion failed at random. Reproduced 200/200 with a payload larger than the pipe buffer, and observed on CI as one grep in a pair failing while its neighbour passed. The status these pipelines want is the consumer's, which is what they return without pipefail.
  • The test harness now scrubs an inherited git environment and observes repos through git --no-ext-diff --no-textconv --no-color with colour keys forced off. Without the first, a caller's GIT_DIR redirected the whole suite at another repository; without the second, a test asserting that something is present fails under a hostile diff driver while its negation passes vacuously.

v0.17.0

Choose a tag to compare

@github-actions github-actions released this 07 Aug 14:56
499a563

Fixed

  • Line specs now work on reset and restore at any context width. Both commands reverse-apply their patch, but the filtered patch was built for forward apply only: deselected + lines were dropped instead of kept as context, so the patch's new side no longer matched the index/worktree and git rejected it with "patch does not apply". git hunk reset <sha>:2 and git hunk restore <sha>:2 failed on any hunk containing more than one change. Only --unified 0 worked, because zero context puts every change in its own hunk and leaves nothing deselected to mishandle. Deselected lines are now kept on whichever side the apply direction matches against, mirrored per direction.
  • git hunk commit --dry-run no longer requires -m. The message check ran ahead of the dry-run branch, so the behaviour documented in --help ("required unless --dry-run") — and already allowed by the argument parser — was unreachable.

Added

  • git hunk add --dry-run and git hunk reset --dry-run report what would be staged/unstaged and exit, touching neither the index nor the worktree. Partial staging is the hardest operation to eyeball after the fact, and the previous workaround — diff <sha>:<lines> — was a different command with different output, so it was easy to preview one spec and then stage another. Output follows restore --dry-run (would stage <sha>[:lines] <file>, would-stage in porcelain) and reports the input hunks rather than post-apply result hashes, which cannot exist without applying.
  • --files-from <path> reads file paths from a file, one per line, with - for stdin. Accepted anywhere --file is, and merges with it. NUL-separated input is detected automatically (NUL cannot occur in a path), so git ls-files -z | git hunk add --files-from - is safe for paths containing newlines without a second flag.
  • git hunk diff -n / --number numbers a hunk's body lines without requiring a line spec, so the numbers a :lines spec expects can be read off the screen instead of counted by hand. The numbered gutter already existed but was reachable only by passing a spec — i.e. only once you knew the answer. -n and a spec share one renderer, so their numbering cannot drift; with both, selected lines keep their > markers. Human output only, a deliberate no-op under --porcelain; plain diff output is unchanged byte-for-byte.
  • Regression tests for line specs at default context on reset (test 237) and restore (tests 525–527, covering insertions, deletions, and a mixed add+delete hunk), plus commit --dry-run without -m (tests 1020–1021). The previously existing line-spec tests for these commands all passed --unified 0, which is why the bug survived; the new tests deliberately do not.
  • diff -n tests (924–929), including one that stages a spec read straight off the gutter to pin the numbering to what add actually selects.

Changed

  • A hash that --file excluded now says so instead of reporting as missing: no hunk matching 'da35d28' in the --file selection (it is in 'internal/cli/root.go'), plus a hint to run the file and hash selections as separate commands. --file scopes which hunks a hash may match (this is also how an ambiguous prefix is disambiguated), so passing hashes alongside it reads as a union but behaves as a filter — and the old message was indistinguishable from a stale hash, sending people to look for the wrong problem. A genuinely unknown hash keeps the original wording.
  • Docs: --file is described as scoping rather than addition on every command that takes hashes, with the two-command idiom for "whole files plus specific hunks" written out; and the staging workflow now warns that a build which skips test files (go build, and equivalents) cannot verify a commit produced by splitting an implementation from its tests.
  • Test harness: completion display capture ends on the explicit dumpbuf signal rather than a fixed quiet period, fixing an intermittent test_completions failure (~1 run in 10 under load, measured 5/5 → 0/6 across the change). run-all.sh and one test no longer use fixed /tmp paths that collide between concurrent runs.

v0.16.0

Choose a tag to compare

@github-actions github-actions released this 04 Jul 00:34
af99533

Changed

  • git hunk commit now builds the commit in a throwaway temp index (GIT_INDEX_FILE) instead of backing up, rewriting, and restoring the real index. Your staged work is never touched: a crash at any point mid-commit (verified with kill -9 fault injection) leaves the index byte-identical with no recovery needed — at worst a stray temp file in /tmp. Hooks still fire normally and see exactly the content being committed. Stale .git/index.hunk-backup files from commits interrupted under older versions are still restored automatically.
  • Internal structure pass over the commit/git-subprocess layer: one error-returning capture core replaces six hand-rolled helper bodies, the parallel *WithEnv helper family is gone, temp-index plumbing moved to the git layer with self-contained ownership, and the hook-path selection logic is a pure, unit-tested function.

Fixed

  • Pre-commit hooks that git add files (lint fixers etc.) no longer leave a phantom staged deletion plus untracked twin after the commit — hook-created paths are synced into the real index, while user-staged work (including staged deletions) is provably untouched.
  • A binary-add failure mid-commit no longer strands transaction state; it aborts cleanly with user state untouched.
  • Paths containing spaces are parsed correctly from patch headers; previously the hook-path cleanup could mistake a committed spaced path for hook-created, and resync diagnostics printed a truncated name.

Added

  • 16 commit edge-case integration tests: overlapping staged/unstaged regions in every flavor, --3way conflict escalation, git fault injection at each transaction step (via a PATH shim), kill -9 crash recovery, index-modifying hooks, binary-commit transactional invariants, and spaced-path handling.

v0.15.3

Choose a tag to compare

@github-actions github-actions released this 03 Jul 22:14
10ab1d3

Fixed

  • zsh hash completion listings no longer columnize side by side in wide terminals — candidates always display one per row (compadd -l), keeping the list --oneline table readable.

v0.15.2

Choose a tag to compare

@github-actions github-actions released this 03 Jul 22:01
316746b

Changed

  • zsh and fish hash completion displays now mirror git hunk list --oneline exactly — aligned file column, empty/N-M line ranges, truncated summaries, list order (by file/line) instead of hash order. The display is sourced from the binary's own --oneline --no-color output, so future list improvements flow into completions automatically.

Added

  • zsh completion listings colour the hunk hash yellow, matching list --oneline (and git log --oneline). Shipped as a soft default: a hunk-hashes list-colors zstyle applied only when the user has no matching style of their own, and skipped when NO_COLOR is set. Menu selection keeps zsh's standard ma= highlight, user-tunable as usual.

v0.15.1

Choose a tag to compare

@github-actions github-actions released this 03 Jul 21:32
22ab283

Fixed

  • zsh completion via git's own git-completion.zsh wrapper (Homebrew git, Apple Command Line Tools) now completes non-empty words. Previously any typed prefix — git hunk add 9<TAB>, git hunk list --sta<TAB>, even a unique hash prefix — completed nothing, because the wrapper's dispatch context breaks the compsys engines behind _describe and _arguments option matching. The _git_hunk bridge is now a self-contained wrapper-native completer built on raw compadd/_wanted/_files, and hash candidates gain oneline-style descriptions (9ab03ef foo 0 0 new file). zsh's builtin _git dispatch and direct git-hunk completion are unchanged.

Added

  • Interactive completion regression tests (tests/test_completions.sh): drives real TAB completion in a pty for both the direct and git hunk wrapper dispatch paths, with hash-prefix overlap guaranteed by construction. Skips gracefully when zsh/zpty or a git-completion.zsh wrapper is unavailable.

v0.15.0

Choose a tag to compare

@github-actions github-actions released this 03 Jul 17:38
fe1e7b5

Added

  • Shell completions for bash, zsh, and fish (completions/), covering both git-hunk and git hunk invocation forms, with live hunk-hash completion from list --porcelain --oneline (staged hashes for reset), --ref completion from git refs, and --file path completion. Release tarballs now ship them and the Homebrew formula installs them.
  • zig build fuzz — a brute-force mutating fuzzer for the diff parser (src/fuzz_driver.zig), plus a corpus-seeded fuzz test in src/diff.zig that replays deterministic regression inputs under zig build test. 8 million mutated inputs survived clean at time of writing. (Coverage-guided zig build test --fuzz is blocked on a Zig 0.16.0 stdlib bug.)
  • zig build docs — CLI documentation facts (commands, flags, arguments, examples) are now single-sourced in src/spec.zig. --help and top-level usage render from it at comptime; the man page's COMMANDS/GLOBAL OPTIONS sections are generated from it; and the skill reference plus all three shell completions are coverage-checked against it. zig build docs -- --check runs in CI as a drift gate.

Changed

  • --help flag display normalized to short-form-first (-h, --help, -u, --include-untracked); example columns aligned consistently across commands.
  • count now documents its accepted no-op flags (--porcelain, --no-color, -v); top-level help now documents -V, --version.
  • The version string is single-sourced from build.zig.zon (build.zig imports it; release builds still override via -Dversion from the tag).

Fixed

  • Help and man page no longer claim --ref "combines with --staged" on add, reset, and restore — those commands reject --staged.
  • Skill reference (commands.md): added previously missing --ref and --3way flag documentation across seven commands; corrected the stale claim that --file paths must match diff output exactly (they resolve relative to the current directory since 0.10.2).