2

As I understand it, Git Stash is a collection of diffs.

So for example, if I have a whole repo with a single file foo.txt

The 32 lines of the file is just

Lorem Ipsum 01
Lorem Ipsum 02
Lorem Ipsum 03
...
Lorem Ipsum 30
a first line: good
a second line: bad

And I committed everything.

Now I create a new branch:

git checkout -b feature123

And now that I am on this branch, I change the word good to better, and bad to worse.

Now, just suppose my program manager comes over and say, "Urgent! We have to change the 01 to 011 and 02 to 022".

So I stash the changes, and then change to the master branch:

git stash
git checkout master

and I changed the 01 to 011, and 02 to 022 as he requested.

And say, I want to incorporate the changes that I "stashed" earlier to the master branch:

git stash apply

but it will error out:

error: Your local changes to the following files would be overwritten by merge:
    foo.txt
Please, commit your changes or stash them before you can merge.
Aborting

Why is that? Can't it just apply the collection of diffs to foo.txt?

(P.S. I can commit first, and then use git stash apply, and be able to apply those diffs to foo.txt. But why not before the commit? So git stash really just saves a collection of diffs -- to repeat precisely: a collection of diffs, and then do a git checkout . (to abandon all changes). Is that true?)

nonopolarity
  • 146,324
  • 131
  • 460
  • 740
  • Did you commit the changes you made before attempting to apply the stash'd changes? – sisyphus Apr 13 '16 at 10:58
  • @sisyphus The question is why the changes need to be commited (or stashed), and why `git stash` can't `apply` patches to changed files. – tobiasvl Apr 13 '16 at 11:04
  • 1
    It's a safety mechanism, I for one am very glad it won't just overwrite my changes, w/o at least having me stage them. – Creynders Apr 13 '16 at 11:06
  • 1
    *[...] Git Stash is a collection of diffs.* The premise of the question is incorrect. – jub0bs Apr 13 '16 at 12:01

2 Answers2

4

Git Stash is a collection of diffs.

Not really. a stash is a collection of commit objects, each of them records a complete snapshot of the project. git stash pop does a merge between the current head, the stash, and the commit the stash is based on.

It would be possible in theory to perform a merge on locally-modified files, but in practice:

  • This would be a really bad idea for merge because it would encourage users to do "evil merges", i.e. have both merge-related an manual changes in the same commit. As a consequence, it is forbidden for a merge to touch locally modified files, and stash uses the same mechanism as merge, hence forbids it the same way.

  • The index makes things more complex than they appear to be. If a file had unstaged content, and you wanted to apply a stash to it, then there would be no proper way to define what should be the content of the index at the end of the stash application. Actually, stash pop is allowed to touch files for which the index matches the working tree.

Note: your problem is not the fact that you are on different branches, but that you have uncommited/unstaged changes. You did these changes while on branch feature123, but they are preserved when switching branches with git checkout master.

Matthieu Moy
  • 15,151
  • 5
  • 38
  • 65
  • say if my change is 1 character in 1 file only, then will the stash be a collection of commit objects, but in this case, just 1 commit object? So if I modified 2 files, then 2 commit objects? What about 1 file but 1 change at line 1 and 1 change at line 30, then... 1 commit object? Or I guess I am trying to get at is, can we view it as... just an unnamed branch off, with all files committed... so we can "cherry-pick" this commit to apply the changes to any other situation? – nonopolarity Apr 13 '16 at 12:58
  • by commit objects, you don't mean a complete commit? that's because I know git will save a whole new file when you commit, so if I create a text file that is 15MB, with each line in it just 80 characters long with a line number, then if I edit the file for just 1 line and commit, the whole directory is now 15MB. Change 1 line again and commit and now the whole directory is 30MB. Now if I make a new feature branch and switch there, edit 1 line, and do a `git stash`, the whole directory just increased a little bit in size (1MB or less). – nonopolarity Apr 13 '16 at 14:10
  • (having said that... if I go back to the master branch, change another line and commit, the whole directory increased only a bit in size. Maybe it is due to compression.) – nonopolarity Apr 13 '16 at 14:22
  • Please distinguish "what is stored" and "how it is stored". Whenever you perform a commit, git stores a commit object in its object database, i.e. you can later recover the whole state at that commit. In practice, this is stored using delta-compression which saves your disk space (but delta-compression is not always applied, you need to run `git gc` or wait for git to run `git gc --auto` for you to get it). – Matthieu Moy Apr 14 '16 at 05:46
  • BTW, one "commit object" is created when you commit (or stash, or ...), but a single commit contains the whole project state, regardless of how many files your modified since last commit. – Matthieu Moy Apr 14 '16 at 05:46
  • by the way, do you have any reference that a stash is a collection of commit objects (vs a collection of diffs)? Isn't it true that a commit object can represent a whole file (a complete file with all the content)? If so, why do we need this complete content... it seems a stash is mainly useful for "applying the diffs" again like cherry-picking a commit – nonopolarity Apr 14 '16 at 08:11
  • 1
    Just check by yourself: launch `gitk --all` after running `git stash`, and play with `git cat-file -p` to see the types of objects. A commit object does not represent just the content of a *file*: it contains meta-data (date, log message, parents, ... and the content of the whole repository : a tree object in Git's terminology). – Matthieu Moy Apr 14 '16 at 13:21
1

man git stash says the following, under pop (which also applies to apply):

The working directory must match the index.

So you are correct, it can apply the collection of diffs to foo.txt, but not if you have unstaged local changes. If you're confused about the difference between the working directory and the index, perhaps this answer can help.

Run git add hei.txt, and you will be able to apply the stash.

tobiasvl
  • 570
  • 4
  • 20
  • so I think the phrase "The working directory must match the index" means that everything must be staged? (or every tracked file must be staged?) Is there really a reason they have to be staged... except the fact that the man page says so? (that is... aside from what the man page says, is there really a good reason for requiring so?) – nonopolarity Apr 13 '16 at 11:11
  • @太極者無極而生 git must know about your local changes (ie. they must be staged to the index) for it to be able to resolve conflicts. When you stage, git creates a ref (which you can see in `git reflog`). When you apply the stash, git merges the stash ref with the index ref. This also works as a safety net in that you can't lose local changes if the stash does something you didn't intend, because git knows about your changes. – tobiasvl Apr 13 '16 at 11:35