Search code examples
gitegitjgit

Git - why can't some clients apply stash when there are staged changes


I stashed some changes for file1. Then I changed something in a distinct file2 and I staged these changes.

When I try to apply the stash I get different behaviors depending on what Git client I'm using:

  • Git Bash (using git stash apply stash@{0}) and SourceTree are successful
  • EGit and GitKraken are not successful

These are the errors when the operations are not successful:

  • EGit

enter image description here

  • GitKraken:

enter image description here

Why is the stash not applied when working with some clients, given the fact that the stashed file and the staged file are distinct?

By the way, I'm asking this because we are trying to implement the "Apply stash" action in a Git client based on JGit, and we have the same error as EGit (which is also based on JGit).


Solution

  • EGit isn't Git, and GitKraken isn't Git. These two appear to be different from Git in different ways, though:

    • EGit is a reimplementation of Git, using JGit. Since it's a totally different source code base, it will drift in and out of sync with Git itself, as Git acquires new features that JGit lacks, or JGit acquires new features that Git lacks. This makes it hard even to guess whether some component X will work in both, and if so, work the same way.

    • GitKraken appears to be a GUI wrapper for Git. If it's literally running Git, then when it does run Git, the effects will match those used in raw Git.

    In your case, you've discovered that JGit's stash-applying code (at least as used by EGit) doesn't match Git's. (Git's stash-applying code was rewritten from shell script to C in Git 2.22.0, first released in June 2019, with some subsequent small changes in behavior, but this particular behavior hasn't changed in Git in a long time: git stash apply has always allowed applying a stash with a "dirty" working tree. I strongly recommend not doing this myself, so EGit's behavior is arguably "better", but it's different.)

    As LeGEC said, GitKraken's complaint is that your index currently does not match your current commit. This, too, is actually probably a wise thing: git stash apply has to run the merge machinery, and the merge machinery needs to use an index. The old script version and C version may differ somewhat here, especially with the new merge-ort code going in, but again, it seems unwise to allow a merge to start if the (main) index for this working tree is out of sync with the commit for this working tree.

    LeGEC's answer has a technical error (perhaps sufficiently papered over 😀 with the word roughly):

    ... and git stash apply is roughly a sequence of git cherry-picks on those commits.

    Unstashing a stash will:

    • apply the index commit's index changes if you use --index, otherwise ignore it entirely;
    • apply the working tree commit's changes using the default (currently, recursive, soon-to-be-ort) merge strategy.

    The merge strategy code does not always do its own checking to make sure that the working tree and index are "clean" before it starts working, in part because this same code gets re-used by the octopus strategy, where it's run sequentially for each commit to be merged. The octopus strategy then notices any conflicts, and aborts the merge entirely if any occur, while the regular recursive/resolve code leaves the merge mess behind in the index and working tree.

    Whether and when applying a stash results in conflicts is impossible to predict without actually trying the merge. The fact that the merge uses the index and the working tree, at least in the current implementation, means that applying a stash with either of those "dirty" (not matching the current commit) can leave you with a mess that is not easily recovered: git reset --hard, which cleanly aborts a failed merge started with a clean setup, destroys the in-progress work that git stash apply messed up.

    In your particular case, apparently the merge goes well, without needing to touch the staged file either. Git has some optimizations here, important for making git merge acceptably fast, to avoid touching the index or working tree for files that don't need anything done to them, and that's probably what let you get away with this.