Skip to content

v0.20.0

Latest

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 conflict markers and left unmerged entries; with --ref, a restore that applied cleanly was silently staged, and one into a file with other unstaged changes failed even where it applied without merging. restore now changes only the worktree: a hunk that applies, directly or merged, leaves the index alone, so without --ref --3way changes nothing (as git restore --merge does on a path with nothing to merge). Only a merge that conflicts touches the index, recording the conflict as git apply --3way and git stash apply do, with what was staged as "ours".

Changed

  • A stash entry's message is the one git stash push writes: WIP on <branch>: <sha> <subject>, or On <branch>: <msg> with -m, instead of git-hunk stash: <paths> or the bare -m text.
  • restore of an untracked file without --force now says restoring it cannot be undone; use --force instead of use --force to delete: restoring part of an untracked file removes only those lines.
  • File sections are parsed into data that hunks share, and patch headers are rendered when the patch is built instead of when the diff is read. A filtered hunk's @@ line is written the way git writes it (a count of 1 is left out, and a side that becomes empty is numbered by the line before it).
  • Messages name the --ref you typed rather than what it was expanded to or a mode it does not have:
    • Nothing to act on: no changes in 'X' (or 'A..B') and no staged changes relative to 'X', instead of no unstaged changes. list and count report an empty ref diff as they do a clean tree: nothing, and 0.
    • A --ref patch that does not apply: error: changes from 'X' do not apply cleanly to the index (try --3way) (or to the worktree for restore), instead of patch did not apply cleanly — the diff from 'X^..X' may conflict with the current state.
    • A range with --staged: error: --staged compares the index with one commit; 'A..B' is a range, instead of --staged cannot be used with a range ref (contains '..').
    • -v suggests git add <path> for a path with no hunk only among unstaged changes; elsewhere the note stands alone.
  • The docs describe --ref X as that commit's changes against its first parent (the empty tree for a root commit) rather than as X^..X with git show semantics, which differ for a merge.
  • --staged and --ref now choose a diff source once, while parsing, instead of a staged/unstaged mode plus a ref string that --ref X rewrote to X^..X. Hashes, ranges and patches are unchanged.