1
$ git init
$ git commit -m 'initial commit needed to stash' --allow-empty
$ touch test
$ git add --intent-to-add test  # aka -N
$ git stash
error: Entry 'test' not uptodate. Cannot merge.
Cannot save the current worktree state

It's not untracked, so --include-untracked has no impact. Is there any way to stash an added but never committed file?

OJFord
  • 8,132
  • 7
  • 52
  • 88
  • Why are you using the `--intent-to-add` option and do you need it? – mnestorov Sep 02 '19 at 13:18
  • Looks like a duplicate question, it's been answered with https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible – Dillip Kumar Behera Sep 02 '19 at 13:35
  • @mnestorov Because I intend to add the file. But not yet, so I'm stashing it. Sure, I can not do that... but that doesn't answer the question ;) – OJFord Sep 02 '19 at 14:14
  • 1
    @DillipKumarBehera That's not a duplicate, unless you can expand on that to explain what I'm missing I don't think it answers this question. – OJFord Sep 02 '19 at 14:16
  • @OJFord If you `add` your file normally, without any `--intent` and then stash it, without commiting, then you should be able to stash a file without it being commited before. – mnestorov Sep 02 '19 at 14:20
  • @mnestorov Understood, and there are other workarounds. My question though is whether files in 'added but not staged' state can be stashed. – OJFord Sep 02 '19 at 15:33
  • @OJFord "added but not staged" doesn't make a lot of sense since when you add a file to the index, you stage it. But what you did with the options above, the answer is no, since git only knows about the path you've specified, but not about the actual contents – mnestorov Sep 02 '19 at 15:37
  • @mnestorov I'll grudgingly accept that as an answer if you like. ('added but not staged' is just my attempt to describe it, I don't know what else to call it to distinguish from usual untracked files?) – OJFord Sep 02 '19 at 15:39
  • Well if we try to `add` a file path, without the contents of the file, then I do not know if git can stash a file, but I would guess it can't since there is no content to stash (at least that's how it makes sense to me :)) – mnestorov Sep 02 '19 at 15:43

1 Answers1

2

Is there any way to stash an added but never committed file?

Only if it's truly added, i.e., there's an actual version in the index. Then git stash will work.

There have, in the past, been some bugs in Git around the --intent-to-add flag. Supposedly they are all fixed now, making it safe to use, but I'd recommend avoiding it unless it's doing something particularly important and/or useful for you today. What it does internally is create an index entity with a flag set, and each Git command that deals with the index is supposed to have special handling for this entity if / as needed:

$ git add --intent-to-add test
$ git ls-files --stage --debug
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0   test
  ctime: 0:0
  mtime: 0:0
  dev: 0    ino: 0
  uid: 0    gid: 0
  size: 0   flags: 20004000

The hash ID here, e69de29bb2d1d6434b8b29ae775ad8c2e48c5391, is the hash of an empty file:

$ git hash-object -t blob --stdin < /dev/null
e69de29bb2d1d6434b8b29ae775ad8c2e48c5391

The flags: 20004000 shows the CE_INTENT_TO_ADD flag from cache.h:

/*
 * Extended on-disk flags
 */
#define CE_INTENT_TO_ADD     (1 << 29)
#define CE_SKIP_WORKTREE     (1 << 30)

What git stash does is to make two (or sometimes three) commits that are not on any branch, then—in effect—run git reset --hard to set your index and work-tree back to the state they would have if you hadn't started working with the index and work-tree.1 The two commits hold the index (staging-area) state, as an ordinary commit / snapshot, and your work-tree state, as another commit / snapshot. The third commit, if it exists, holds any untracked files, possibly including ignored files. None of these commits have any room for these extended flags shown by git ls-files --debug, so the CE_INTENT_TO_ADD flag simply cannot be preserved.

Perhaps git stash could try to handle the flag by pretending it's not set at all (so that it writes an empty test file instead, then removes the index entry entirely). This would be mostly consistent: the file is, after all, tracked as an empty file. It just has this special "intent to add" status, plus the all-zero cache information since it doesn't exist as a file-system file. You would lose the special "intent to add" status in the process, of course. So the end result would be the same as if you'd just added empty files instead.


1This used to be exactly what it did, but then git stash got fancied up to allow for pathspec arguments. For several Git versions afterward, this kind of git stash could occasionally lose data. All the bugs here have been fixed—I think—but in general I don't recommend doing this kind of pathspec-based stash. In fact, I recommend avoiding git stash entirely except for some very short-term purposes. Stashes are, after all, just commits that don't have a lot of nice ways to find them again later. Make real commits, which do have good ways to find them again later.

torek
  • 330,127
  • 43
  • 437
  • 552
  • A very informative answer, thanks! For what it's worth, my main use of stash is indeed short-lived - it's rebasing with `--autostash`, which is how I ran in to this. I like `add -N` so that I don't forget to stage files later (since `add -p` won't include untracked), but I also very much like my `fixup` alias for `commit -m 'fixup! ' && rebase ^` (essentially) but only today found that the two don't go together. – OJFord Sep 02 '19 at 21:16
  • stash-for-rebase is one of the better uses of stash - but I kind of wish it just made one or two regular commits of its own, for the rebase! You'd still have issues here though, as the intent-to-add flag simply can't be saved as things stand. – torek Sep 02 '19 at 21:50