Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>/I'm keeping the first long reply below (might try to move it to a new question later), but now that there's a "reproduce example" I'll go through that. Let me make a list of points here though.</p> <ol> <li><p><code>git stash</code> always stashes <em>both</em> the index and the work dir. One might think <code>--keep-index</code> makes it stash more, or changes the way the stashed value is handled on a <code>pop</code>. It doesn't! Both <code>git stash apply</code> and <code>git stash pop</code> mix together the separated index change by default. Adding <code>--keep-index</code> <em>does not change this</em>. Only the <code>--index</code> argument to <code>apply</code> and <code>pop</code> tries to avoid mixing them.</p></li> <li><p>The "work directory" that <code>git stash</code> saves amounts, in effect, to the <em>change</em> from the current <code>HEAD</code>. This means that if the <em>index</em> has a change from <code>HEAD</code>, but the <em>current work directory</em> does not, there's really no change saved in the "WIP on <em>branch</em>..." commit. (This is, I think, a bug in <code>git stash</code>. I have sent a test case and possible fix to the git mailing list. For "normal" cases it's fine, but if you've split out some parts and then want to recover your exact state later with <code>git stash branch</code>, it drops working directory state. And it's causing your problem here.)</p></li> <li><p>Applying a stash tries to make changes to the <em>current state</em> that mirror the changes in the <em>stashed state</em>. This can be complicated, because the current state is not necessarily anything like it was when you saved the stash.</p></li> </ol> <p>Here's what <code>git-gui</code> is doing. At the time you fire it up you have this (actual commit numbers will of course vary). The unlabeled "WIP on master" is the "first" stash, now <code>stash@{1}</code>.</p> <pre><code>$ git stash list stash@{0}: WIP on master: c93c8fe tobeamended123 stash@{1}: WIP on master: c93c8fe tobeamended123 $ git log --decorate --oneline --graph --all 'stash@{1}' * 3d01942 (refs/stash) WIP on master: c93c8fe tobeamended123 |\ | * 6be9135 index on master: c93c8fe tobeamended123 |/ | * de8038c WIP on master: c93c8fe tobeamended123 | |\ |/ / | * 3db6cfc index on master: c93c8fe tobeamended123 |/ * c93c8fe (HEAD, master) tobeamended123 * 828d5cf base123 </code></pre> <p>Now in <code>git gui</code>, when you select "amend last commit", it finds the ref for the HEAD commit (<code>c93c8fe</code>, in my case). It does not actually <em>do</em> anything to it (yet). But as soon as you click on <code>f3</code> to unstage it, it does something: it grabs the <em>previous version of <code>f3</code></em> (I'm not sure what the gui uses underneath, my guess would be <code>HEAD^</code>'s copy) <em>and stuffs it into the index</em>. If you examine <code>f3</code> it still has the extra line in it, but if you <code>git show :0:f3</code> to see the version in the index, it no longer has that line.</p> <p>Note that no refs have changed due to gui-mouse-clicks, and there are no new commits. All the action has taken place inside the index.</p> <p>Next, you went back to the command line and ran:</p> <pre><code>$ git stash save --keep-index </code></pre> <p>This made a third pair of commits, one with the index and one with the current directory. The index version has the extra line in <code>f1</code> and <code>f2</code> and lacks the extra line in <code>f3</code>. The current-directory version <em>should</em> (one would think) have the extra line in all three files—but, alas, it does not, because <code>git stash save</code> compares current dir vs <code>HEAD</code> commit, and the extra line is there in the <code>HEAD</code> commit, so it's not in the stashed version.</p> <p>Unforunately, you used that <code>--keep-index</code> argument, so <em>now</em> the working directory version is the same as the stashed index version. File <code>f3</code> no longer has the extra line.</p> <p>From here on, the problem persists (the change is gone, <code>--keep-index</code> tossed it). You can of course recover it from the original commit ("tobeamended123"). But that's where things went wrong in this case: the command-line <code>stash</code> saved the index, and then compared the work directory against <code>HEAD</code>, which had not changed, so did not save the (non-change) to <code>f3</code>.</p> <hr> <p>I don't see disaster, but I see something confusing, which I bet confused you. I don't know why you used <code>--keep-index</code> above. (In fact, I'm not sure what use-case <code>--keep-index</code> might be intended for<sup>1</sup>, and it seems to me that <code>apply</code> and <code>pop</code> should probably default to <code>--index</code>, but that's another matter entirely....) And, you made four total stash "pushes", and only "popped" one, leaving three to go.</p> <p>[<sup>1</sup>I found the intended use-case, right there in the documentation: for testing what is currently in the index, before committing it. But wait, huhwha?, <code>--keep-index</code> <em>does</em> commit it, on the <code>stash</code> ref. You might as well just commit anyway, using <code>git checkout -b test-stash</code> to keep it safely segregated until you're happy with it. If you test it and it <em>fails</em> and you need to modify it, that stash is going to have conflicts. If you test and it <em>works</em> you can just pull / fast-forward-merge the commit that worked, into your earlier branch.]</p> <h2>The "tl;dr" short answer</h2> <p>Run <code>git stash list</code>. You'll see a list of:</p> <pre><code>stash@{0}: WIP on master: ab0d18d Setup of alarms ... stash@{1}: WIP on master: ... </code></pre> <p>items. Use <code>git stash apply <em>--index</em> 'stash@{<em>n</em>}'</code> (the <code>--index</code> is optional) to try to apply each saved stash by name-and-number, without popping any of them. It's a stack, with <code>stash@{0}</code> the most recently pushed and (by this point) <code>stash@{3}</code> the first (longest-ago) pushed.</p> <p>The apply-without-pop means you can <code>git reset --hard</code> to get back to <code>master</code> and ready to <code>git stash apply</code> a different stash. (Be sure you start the whole sequence with a clean work directory, perhaps by adding another <code>git stash</code>, although that could get confusing again. :-) )</p> <p>If you've made a particularly big mess, you can use use <code>git stash branch <em>name</em> 'stash@{<em>n</em>}'</code>. This is a big, fast, effective hammer whose main drawback is that you have to invent a branch name. (You can <code>git stash show</code> the stashes to see what's in them, to help you come up with names.) Don't let this scare you, as you can always rename the branch or even delete it later. See the long description for exactly how this works.</p> <p>When you're all done with all your stashes, use <code>git stash clear</code> to wipe them all out.</p> <h2>Regarding <code>git commit --amend</code> vs <code>git stash</code></h2> <p>These are actually somewhat independent. The <code>commit --amend</code> works on a commit-chain based on whatever branch you're on. Let's say you're on <code>master</code> and the chain looks like this (in <code>git log --graph --oneline --decorate</code>, or <code>gitk</code>):</p> <pre><code>* 67dec43 (HEAD, master) "amendme" commit * 9c37840 previous commit </code></pre> <p>You edit and <code>git add</code> some things—I will change file <code>f3</code> and add it—and then run <code>git commit --amend</code>. This takes the index and makes a new commit, but the new commit's parent is one back from where <code>master</code> was, i.e., the <code>previous commit</code> above. Now the log output looks like this:</p> <pre><code>* 68c51f3 (HEAD, master) replacement for "amendme" commit * 9c37840 previous commit </code></pre> <p>What you can't see (because there's no branch label on it) is that <code>67dec43</code> is <em>still in there</em> (until it expires and gets garbage collected), but if you tell <code>git log</code> to look there it will:</p> <pre><code>$ git log --graph --decorate --oneline master 67dec43 * 68c51f3 (HEAD, master) replacement for "amendme" commit | * 67dec43 "amendme" commit |/ * 9c37840 previous commit </code></pre> <p>You have a branch coming off "previous commit", with the <code>master</code> label at the new replacement commit and the "amendme" commit on an unlabeled branch.</p> <p>Let's do this again, with a stash in place this time. I start with a "known bad" file in <code>f3</code> in the "amendme" commit. I then put in a second (but still not right) <code>f3</code> and run <code>git stash</code>. Finally, I fix <code>f3</code> "for real" and use <code>--amend</code>. The stash keeps a reference to the now-unlabeled branch, because a stash <em>is</em> a new commit (really, two). Here are the last few steps:</p> <pre><code>$ git log --graph --decorate --oneline * 3c97241 (refs/stash) WIP on master: 67dec43 "amendme" commit |\ | * f3a50e9 index on master: 67dec43 "amendme" commit |/ * 67dec43 (HEAD, master) "amendme" commit * 9c37840 previous commit * 84408ef base $ echo 'better changes for f3' &gt; f3 $ git add f3 $ git commit --amend -m 'replacement for "amendme" commit' $ git log --graph --decorate --oneline --all * c1f1042 (HEAD, master) replacement for "amendme" commit | * 3c97241 (refs/stash) WIP on master: 67dec43 "amendme" commit | |\ | | * f3a50e9 index on master: 67dec43 "amendme" commit | |/ | * 67dec43 "amendme" commit |/ * 9c37840 previous commit * 84408ef base </code></pre> <p>If you try to apply the stash, there will be a conflict (because the stash changes file <code>f3</code>, with my intermediate, "not completely bad, but not better either" version):</p> <pre><code>$ git stash apply git stash apply Auto-merging f3 CONFLICT (content): Merge conflict in f3 $ git reset --hard master HEAD is now at c1f1042 replacement for "amendme" commit $ git stash apply --index Auto-merging f3 CONFLICT (content): Merge conflict in f3 Index was not unstashed. $ git reset --hard master HEAD is now at c1f1042 replacement for "amendme" commit </code></pre> <p>These are the same as any other conflict when bringing commits in, such as <code>cherry-pick</code> or <code>merge</code>, and you resolve them the same way.</p> <p>If you like, you can stick a branch or tag label on the "amendme" commit:</p> <pre><code>$ git branch master-old 67dec43 $ git log --graph --oneline --decorate --all * c1f1042 (HEAD, master) replacement for "amendme" commit | * 3c97241 (refs/stash) WIP on master: 67dec43 "amendme" commit | |\ | | * f3a50e9 index on master: 67dec43 "amendme" commit | |/ | * 67dec43 (master-old) "amendme" commit |/ * 9c37840 previous commit * 84408ef base </code></pre> <p>and now it's easily available for reference. You can then check it out and <code>git stash pop --index</code> that particular stash; this is guaranteed to work (hence the <code>pop</code> is safe, although you might want to <code>apply</code> anyway until you've done several of these). See also "Using <code>git stash branch</code>" below, which automates this.</p> <h2>How stash works, the long version</h2> <p>Let's step back a bit. I want to show a simplified example, with just three files.</p> <p>Let's make a temp dir and git repo and commit a starting point, with three one-line-long files:</p> <pre><code>$ mkdir /tmp/tt; cd /tmp/tt; git init ... # create files f1, f2, f3; git add ... $ git commit -m base [master 84408ef] base 3 files changed, 3 insertions(+) create mode 100644 f1 create mode 100644 f2 create mode 100644 f3 $ ls f1 f2 f3 $ cat f1 f2 f3 this file stays the same this file changes in the index this file changes in the WIP </code></pre> <p>Now, let's make the changes happen:</p> <pre><code>$ echo more for f2 &gt;&gt; f2; git add f2 $ echo more for f3 &gt;&gt; f3 </code></pre> <p>At this point, <code>f2</code> is changed and staged:</p> <pre><code>$ git diff --cached diff --git a/f2 b/f2 index 78991d3..3a2f199 100644 --- a/f2 +++ b/f2 @@ -1 +1,2 @@ this file changes in the index +more for f2 </code></pre> <p>and <code>f3</code> is changed but not staged:</p> <pre><code>$ git diff diff --git a/f3 b/f3 index d5943ba..188fe9b 100644 --- a/f3 +++ b/f3 @@ -1 +1,2 @@ this file changes in the WIP +more for f3 </code></pre> <p>Here <code>diff --cached</code> shows the staged stuff (in the index) and <code>diff</code> without <code>--cached</code> shows the unstaged stuff.</p> <p>Now, let's <code>git stash</code> (the default op is to <code>save</code>). The <code>stash</code> will add two commits to the repo. The first one is <em>just</em> the stuff staged so far (if there's nothing staged, <code>stash</code> forces in a no-changes commit) and the second is a merge commit, of that-plus-work-dir. So:</p> <pre><code>$ git stash Saved working directory and index state WIP on master: 84408ef base HEAD is now at 84408ef base $ git log --graph --oneline --decorate --all * 753a6c8 (refs/stash) WIP on master: 84408ef base |\ | * 36b23f2 index on master: 84408ef base |/ * 84408ef (HEAD, master) base </code></pre> <p>That first one, <code>index on master</code>, has my change to <code>f2</code>:</p> <pre><code>$ git show 36b23f2 [snip] diff --git a/f2 b/f2 index 78991d3..3a2f199 100644 --- a/f2 +++ b/f2 @@ -1 +1,2 @@ this file changes in the index +more for f2 </code></pre> <p>The second has both changes (<code>f2</code> and <code>f3</code>), but is a merge commit, so <code>git show</code> shows a combined diff, only showing <code>f3</code>:</p> <pre><code>$ git show 753a6c8 [snip] diff --cc f3 index d5943ba,d5943ba..188fe9b --- a/f3 +++ b/f3 @@@ -1,1 -1,1 +1,2 @@@ this file changes in the WIP ++more for f3 </code></pre> <p>(Aside: if you want to compare any merge <code><em>M</em></code> against each parent, use <code>git show -m <em>M</em></code>. For instance, <code>git show -m 753a6c8</code> first diffs <code>753a6c8^1</code>-vs-<code>753a6c8</code>, then <code>753a6c8</code>^2-vs-<code>753a6c8</code>.)</p> <h3>What's with this <code>--keep-index</code> thing?</h3> <p>Normally, after you do a <code>git stash</code>, you have a clean directory so there's nothing to "re-stash", as it were:</p> <pre><code>$ git status # On branch master nothing to commit, working directory clean $ git stash No local changes to save </code></pre> <p>But you asked stash to <code>--keep-index</code>. That still makes the usual stash entry, but then it <em>extracts the contents of the index commit</em>, putting that into both the working directory and the index. Let's pop off the current stash, look at the state (<code>git status</code>—<code>pop</code> does the <code>status</code> automatically—and then <code>git log --graph --oneline --decorate --all</code>), and see that we're back to the work in progress state but there's nothing staged this time:</p> <pre><code>$ git stash pop ... $ git log --graph --oneline --decorate --all * 84408ef (HEAD, master) base </code></pre> <p>Now let's re-stage <code>f2</code> and re-do the <code>stash save</code>, but this time, with <code>--keep-index</code>:</p> <pre><code>$ git add f2 $ git stash save --keep-index Saved working directory and index state WIP on master: 84408ef base HEAD is now at 84408ef base </code></pre> <p>Looks the same as before ... but not quite:</p> <pre><code>$ git status # On branch master # Changes to be committed: # (use "git reset HEAD &lt;file&gt;..." to unstage) # # modified: f2 # </code></pre> <p>Use <code>git log --graph --oneline --decorate --all</code> and you'll see basically the same thing (with different commit hashes) as before: <code>stash</code> committed the index, then committed a merge-commit of the work tree. But this time it <em>also</em> re-extracted the index, so now you have "changes to be committed". <em>This is just <code>f2</code>, not <code>f3</code>.</em></p> <p>This means you can (somewhat pointlessly) <code>git stash save</code> again. And you did! Let's use that log-graph-one-line-decorate thing (I use it a lot), both before and after:</p> <pre><code>$ git log --graph --oneline --decorate --all * 7efe9a6 (refs/stash) WIP on master: 84408ef base |\ | * 76c840e index on master: 84408ef base |/ * 84408ef (HEAD, master) base $ git stash save Saved working directory and index state WIP on master: 84408ef base HEAD is now at 84408ef base $ git log --graph --oneline --decorate --all $ git lola * eb383e0 (refs/stash) WIP on master: 84408ef base |\ | * aba15e6 index on master: 84408ef base |/ * 84408ef (HEAD, master) base </code></pre> <p>Looks the same before-and-after, at first blush. But <strong>look closely</strong> at the SHA-1 IDs. They changed! Before the second <code>git stash save</code>, <code>refs/stash</code> named commit <code>7efe9a6</code>. Now it names <code>eb383e0</code>!</p> <h3>The reflog (or, pay attention, it's getting complicated)</h3> <p>OK, it's not <em>that</em> bad, but now you have to learn about the "reflog". Where did the other <code>stash</code> go? The answer is, it's been "pushed" and has disappeared into the reflog. There's a minor extra wrinkle, too: the "stash" is not a regular branch or tag. Instead, it's in <code>refs/stash</code>. So here's one way to see it, using <code>git log -g</code>, which means "look at reflogs":</p> <pre><code>$ git log -g --oneline refs/stash eb383e0 refs/stash@{0}: WIP on master: 84408ef base 7efe9a6 refs/stash@{1}: WIP on master: 84408ef base </code></pre> <p>Aha, there they are, both <code>7efe9a6</code> and <code>eb383e0</code>. They have "user form full names" (<code>refs/stash@{1}</code> for instance) that are a bit of a pain to use. Fortunately <code>stash</code> works (unless you name a branch <code>stash</code>) to get the "top-most" <code>{0}</code> one, and you can write <code>stash@{1}</code> for the other. Or we can go for full-blown automation:</p> <pre><code>$ git log -g --pretty=format:%H refs/stash </code></pre> <p>This dumps out their full hashes, which we can use as arguments to <code>git log --graph --decorate</code>, to get this:</p> <pre><code>$ git log --graph --oneline --decorate $(git log -g --pretty=format:%H refs/stash) * eb383e0 (refs/stash) WIP on master: 84408ef base |\ | * aba15e6 index on master: 84408ef base |/ | * 7efe9a6 WIP on master: 84408ef base | |\ |/ / | * 76c840e index on master: 84408ef base |/ * 84408ef (HEAD, master) base </code></pre> <p>That's all just to see what's still "in there", in the repo.</p> <p>(Or, of course, you can use <code>gitk</code>, as you did, to see them. <code>gitk</code> is smart enough to look for the stash reflogs.)</p> <p>(Aside: you can also use <code>git reflog show refs/stash</code>. The <code>reflog show</code> sub-command just runs <code>git log -g --oneline</code>.)</p> <h3>Back to our problem, again</h3> <p>Now that we've done a first <code>git stash save --keep-index</code> and then a pointless <code>git stash save</code>, now what?</p> <p>Well, we can <code>git stash pop</code> to get the most recent (top-most of stack) stash back:</p> <pre><code>$ git stash pop # On branch master # Changes not staged for commit: # (use "git add &lt;file&gt;..." to update what will be committed) # (use "git checkout -- &lt;file&gt;..." to discard changes in working directory) # # modified: f2 # no changes added to commit (use "git add" and/or "git commit -a") Dropped refs/stash@{0} (eb383e050d150a8ce5b69a3662849ffdd7070c89) </code></pre> <p>What happened to <code>f3</code>? As we noted earlier, the second <code>git stash save</code> saved <em>only</em> the "kept index", i.e., just the changed <code>f2</code>. What we need is to get back to the <em>first</em> stash.</p> <pre><code>$ git stash pop error: Your local changes to the following files would be overwritten by merge: f2 Please, commit your changes or stash them before you can merge. Aborting </code></pre> <p>That's not much help, is it? :-)</p> <p>If you're not sure what you're doing, now is a good time to make a "save stuff" branch (you can always delete it later). Just <code>git checkout -b help-me-spock</code> or whatever, add, and commit. This stuff is now on a branch and easier to keep track of. But we know what we are doing, and that we have <code>f2</code> in the <em>other</em> stash. So we can just wipe this out:</p> <pre><code>$ git reset --hard </code></pre> <p>Now we're back to the state we would have had, if we had done just one <code>git stash save</code>, without <code>--keep-index</code>: we're on <code>master</code>, with the working directory clean, and a single stash saved. We can <code>git stash list</code> it, <code>git stash show</code> it, and so on. So now:</p> <pre><code>$ git stash pop --index # On branch master # Changes to be committed: # (use "git reset HEAD &lt;file&gt;..." to unstage) # # modified: f2 # # Changes not staged for commit: # (use "git add &lt;file&gt;..." to update what will be committed) # (use "git checkout -- &lt;file&gt;..." to discard changes in working directory) # # modified: f3 # Dropped refs/stash@{0} (7efe9a65c44156921bbbcb6a3df4edc5cb44492b) </code></pre> <p>and we have everything back. (Or, without <code>--index</code>, <code>stash</code> will just apply all the changes to the working directory, rather than restoring index and work-dir.)</p> <h3>Using <code>git stash apply</code></h3> <p>The nice thing about <code>git stash pop</code> is that it applies and then drops the top-most stash entry. The annoying thing is, it applies and then <em>drops</em> the entry. If you use <code>git stash apply</code> instead, it hangs on to it.</p> <p>Among other things, that's quite handy if you misspell <code>--index</code> as <code>--keep-index</code> (I did more than once, while typing this), or leave it out and later decide it would have been nice to use it. You can <code>git reset --hard</code> and re-do the <code>apply</code>.</p> <p>If you're done with a stash entry, <code>git stash drop <em>entry</em></code> will remove it from the reflog. For instance, suppose you do <code>git stash apply --index 'stash@{1}'</code> and then decide it's all good and want to <code>add</code> and/or <code>commit</code> it and then forget about that stash. You can then <code>git stash drop 'stash@{1}'</code>. The drawback is that this renumbers the rest: what was <code>stash@{2}</code> becomes <code>stash@{1}</code>, and so on. I find it's sometimes easier to keep them all around and use <code>git stash clear</code> to get rid of all of them at once, at the end.</p> <h3>Wait a minute, what's with these <code>--index</code>-es?</h3> <p>By default, <code>git stash apply</code> and <code>git stash pop</code> take the saved index ("changes staged for commit") and work-in-progress ("changes not staged for commit") and put them both into effect <em>as work-in-progress only</em>. Often that's fine, but if you've carefully staged some bits and left others unstaged, you might well want all that back. The <code>--index</code> argument to <code>apply</code> (and <code>pop</code>) tries to do that. Sometimes it turns out to be "too hard". In that case, you have two options: leave out <code>--index</code>, or use <code>git stash branch</code>.</p> <h3>Using <code>git stash branch</code></h3> <p>I mentioned above, in the section on amended commits vs stashes, that you can add a new branch label to a commit that has a stash on it, and then <code>apply</code> or even <code>pop</code> the corresponding stash, <em>with</em> <code>--index</code>, and it will <em>always</em> work. The reason is simple: the stash is a merge commit of the index and WIP, corresponding to the commit they're on. If you check that commit out (as a "detached HEAD"), the index and WIP <em>will</em> apply cleanly.</p> <p>So, suppose you add a new branch name at the commit in question, and get on the new branch (<code>git checkout -b <em>newname</em></code>). Now apply (and pop-off) the stash, using <code>--index</code>: you're now in <em>exactly</em> the same state you were when you first ran <code>git stash save</code>, except that the branch has a different name. And that's what <code>git stash branch</code> does: you give it a new branch name and tell it which stash to use (the default is <code>refs/stash</code>, A.K.A. <code>stash@{0}</code>). It uses that stash entry to find the parent commit, attaches the branch name there, and then does a <code>git stash pop --index</code>.</p> <p>At this point you can use <code>git status</code>, <code>git diff --cached</code>, <code>git diff</code>, etc., to see what's in the index and what's not, decide what else if anything to add, then <code>git commit</code> to add new stuff to the new branch you've created.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload