Search code examples
gitgit-stash

git stash partial failure due to wrongly ordered arguments and lost untracked files


I had some modified files and I had 2 untracked files.

I wanted to stash some of the modifications and the untracked files All the thinks I wanted to stash were in the same directory so I used that and a wild card on the end to achieve stashing all the files within it.

so I ran

git stash push -u  -- File/Path/To/FolderOfFilesIWantCommited/* -m "My Message"

This returned this

Saved working directory and index state WIP on [Branch Name]: [Commit Sha1] [commit message]
fatal: pathspec '-m' did not match any files
error: unrecognized input

This message is why I said 'partial failure' in the title.. it isn't really clear what has gone on here

I realised what I should have ran was

git stash push -u -m "My Message"  -- File/Path/To/FolderOfFilesIWantCommited/* 

However after the failed first attempt all of my modifications remained unstaged but present. However the 2 untracked files had disappeared. I could really do with them back, so any help would be greatly appreciated

Just an added note: I ran this in Powershell using PoshGit, which be the cause of one or more of the error messages. Thought I'd include this as it might confuse others


Solution

  • TL;DR: You've run into the bug fixed in commit 833622a945a6, "stash push: avoid printing errors", which first appeared in Git 2.18.0. If your Git is at least 2.16.2, you're OK, otherwise you may have run into the rather more severe bug fixed in commit bba067d2faf0, "stash: don't delete untracked files that match pathspec" but never called out in the release notes.

    The push sub-command, which accepts pathspecs, first appeared in Git 2.13.0. It was quite broken if run from a subdirectory until it was fixed in 2.13.2 / 2.14.0. And, it behaved badly until 2.16.2: it could run git clean over too many files. Since git clean removes untracked files (optionally including the ignored subset of these), and untracked files aren't committed (except for the special -u / -a commit), they can be just gone at this point.

    If you're OK—if your Git is at least 2.16.2, which I think it is—just ignore the complaint; git stash pop the stash and re-run the command with corrected arguments, to get the stash named the way you asked. If not, the files erroneously cleaned by git clean cannot be recovered through Git at all (but I think, given what I think you ran plus the error message, that you are OK).

    (I recommend avoiding tricky use of git stash in most cases—it's usually better just to make a temporary commit. You will also avoid tripping over these kinds of bugs.)

    Long

    First, a quick side note: what git stash does is make (or use) commits, specifically either two (regular stash) or three (stash -u or stash -a) commits that are on no branch. These two or three commits hold:

    • the index state;
    • the work-tree state;
    • any untracked files, if using -u or -a.

    git stash push, and some of its bugs

    git stash push -u  -- File/Path/To/FolderOfFilesIWantCommited/* -m "My Message"
    

    [which then failed with]

    Saved working directory and index state <msg>
    fatal: pathspec '-m' did not match any files
    error: unrecognized input
    

    This makes sense because -- indicates the end of options, after which all strings are taken as path-names or pathspecs (a generalization of path names in which things like * mean "all files"). In this case you gave Git these three pathspecs:

    • File/Path/To/FolderOfFilesIWantCommited/*
    • -m
    • My Message

    These are the specific files that Git should save in the commits it makes. There are some odd wrinkles here due to the fact that in the general case, any commit saves every (tracked) file. We can mostly ignore this fact—while git stash save still saves every tracked file in the two main commits, what the path-spec does is tell git stash which version to save in the work-tree commit: should it save the work-tree version, or the index version? If the pathspec matches matches a tracked file, the work-tree version is the version saved; otherwise the index version is the version saved. (The index commit still saves all index contents as they are at the time you run git stash, using git write-tree, as usual.)

    It's the third commit, made by -u or -a, where the pathspec matters most for you here: it limits the commit to contain only the matching untracked files, rather than all untracked (and maybe ignored, with -a) files. This is also where the nasty bug comes in: Having saved the files' contents in commits, git stash runs, in essence, git reset --hard and git clean, to set the index and work-tree states back to the way they were, and remove the files saved in the extra commit.

    I believe the fix added in 2.16.2 is what produces the complaint fixed in 2.18.0. If so, this means your git stash push did not clean away too many files; instead, it cleaned the right files—those saved in the third commit—and then complained because your pathspec matched only untracked files and/or because of the extra arguments that matched no files.

    In an extreme case, if you just want to get the files in the third "u" commit without using git stash, consider running git show stash^^3 | git apply (note: this is git show stash, with the word show first, not git stash show!). The third commit that git stash made is the third parent of refs/stash, hence stash^3. In PowerShell I believe this must be written with a doubled ^. You can run git show stash^^3 (with the same doubling) to view it before applying it.