15

The short version of this question is this: how can I pop the git stash without triggering an auto-merge?


Now for the longer version...

Consider the following toy example of an alternative to git stash ... + git pull ... + git pop.

First, git status shows that the only modification in the working directory is to some tracked file foo.

# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   foo
#
no changes added to commit (use "git add" and/or "git commit -a")

Now, in order to reset the working directory to a clean state, as a pre-requisite for running git pull, I temporarily rename the modified file foo (to some untracked name), and restore the version of foo in HEAD...

% mv foo foo.$(date +%Y%m%dT%H%M%S)
% git checkout foo
% git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#   foo.20130508T110014
nothing added to commit but untracked files present (use "git add" to track)

OK, now I run git pull, which, for the sake of this example, we may assume is a fast-forward:

% git pull

Lastly, I restore the temporarily renamed foo.

% mv foo.20130508T110014 foo

...and I'm back to

% git status
# On branch master
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   foo
#

This is the "moral equivalent" of a git stash save + git pull + git stash pop, EXCEPT that the former, and not the latter, is immune to "merge conflicts", like this one:

% git stash save 'WIP'
% git pull
% git stash pop
Auto-merging foo
CONFLICT (content): Merge conflict in foo

How can I replicate the rename-checkout-pull-rename sequence above using git stash save + ... + git stash pop, without triggering an auto-merge?

Incidentally, the rename-checkout-...-rename routine more closely represents what I expect from a command called stash. In other words: save the state of my working directory now, and replace it later. There's no "merge" in this picture.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
kjo
  • 27,601
  • 42
  • 124
  • 225
  • 3
    It's not the moral equivalent. If you `stash pop`, changes will be merged. If you `mv` your file back, any changes that happened in the origin repository will be lost and overwritten by your file version. – knittl May 08 '13 at 15:43
  • I dont know why you are renaming the filed and all those see my answer here http://stackoverflow.com/questions/16180916/git-three-different-repositories-with-common-files/16181046#16181046 hope it helps – uday May 08 '13 at 15:43
  • @uDaY: I can't find how to avoid the auto-merge from that answer. – kjo May 08 '13 at 16:51
  • @knittl: that's precisely the point of the question: how to avoid the auto-merge when popping (or how to achieve exactly the same effect of the `rename+checkout+pull+rename` sequence using `git stash` somehow). – kjo May 08 '13 at 16:53
  • @kjo: for every file in the stash or just a few specific files? – knittl May 08 '13 at 17:01
  • may be I found possible duplicate http://stackoverflow.com/questions/3040833/how-to-stash-only-one-file-out-of-multiple-files-that-have-changed – uday May 08 '13 at 17:35
  • @knittl: pls see the answer I just posted to the question – kjo May 08 '13 at 21:31

4 Answers4

8

I belatedly realized that git already provides an extremely simple solution to the problem that motivated this question (namely an automatic merge with the potential to put the repository in a "unmerged state").

All one needs to do is to use

git stash branch <branchname> [<stash>]

instead of git stash pop (or git stash apply). Where <branchname> is the name of a new branch created by git for the purpose of applying the stashed changes.

This pops the stash in a way that is guaranteed to be free of conflicts.

user7440787
  • 754
  • 4
  • 20
kjo
  • 27,601
  • 42
  • 124
  • 225
  • The resulting branch also doesn't have any of the pulled commits on it. – jthill Feb 08 '14 at 22:44
  • 1
    @jthill: indeed. With some hindsight now I see that at the root of this question was a naive understanding of the `git stash` function as a way to "put away my work, do something else, and *come right back* to where I was." This naive picture broke down upon discovering that, for example, a `git pull` after the `get stash` could make it impossible to "come right back" to where I was with a simple `git pop`: there may be conflicts to resolve. So I was casting about for a way to "come right back". Later I realized that the simplest way to achieve this was to create a branch as a safe,... – kjo Feb 08 '14 at 22:58
  • ...conflict-free spot to pop the stash. It was while exploring this idea that I discovered that `git` already had this feature. I think I missed it earlier because I didn't understand `git` branches very well yet, and wasn't too comfortable with them. (I still don't understand branches entirely, but I think I understand them better now than I did then!) – kjo Feb 08 '14 at 23:00
5

stash merges, that's just how it works.

You can achieve the non-merge-y stash with with write-tree read-tree and checkout-index. Here's a worked example for doing this to achieve a pristine test environment.

To just brute-force a no-merge stash apply, you could e.g.

git read-tree stash^{tree}
git checkout-index -af
Community
  • 1
  • 1
jthill
  • 42,819
  • 4
  • 65
  • 113
  • Thanks, though I confess the `git` code in that example flies a few miles above my head... It's certainly not clear to me that it is superior to the 3-line solution I posted (which, admittedly, is a hack). – kjo May 08 '13 at 22:02
  • 1
    It's not clear to me, either -- doing the throwaway merge then undoing the damage has a real advantage in familiarity, learning to use read-tree and write-tree and checkout-index has its own. What I linked to is for a different situation, adapting it here would produce a simpler sequence. Basically I'm just leaving breadcrumbs, I've found learning the lowlevel commands to be rewarding and surprisingly easy. git is, it turns out, _very_ simple at its core, but getting into that stuff is a bit of an acquired taste. – jthill May 08 '13 at 22:34
  • I'm with you: I'm all for going straight for the plumbing, but my git-fu is not there yet... The `git checkout ...` approach is just *slightly* less inscrutable to me at the moment. AFAICT, the only net difference between the two approaches is that with the `git checkout ...` approach `ORIG_HEAD` ends up pointing to `HEAD~`, whereas with your approach it ends up pointing to `HEAD`. – kjo May 09 '13 at 21:36
  • Where is the `...{tree}` syntax (as in `stash@{0}{tree}`) defined? – kjo May 10 '13 at 15:59
  • 1
    It's in [the gitrevisions manpage](https://www.kernel.org/pub/software/scm/git/docs/gitrevisions.html) but even though what I've been doing works it's just a posterior extract and it's uglier than the right way to boot. I fixed it above to do the same thing but by the book. – jthill May 10 '13 at 16:27
  • I thought I'd let you know that I just discovered an already built-in, and trivially simple, solution to my question. I posted it as my second answer to this question. (Given that the `branch` subcommand of `git-stash` had been already available for some time `git`, and that its documented rationale is precisely the use case I had in mind, I realize that I stated my question in a way that completely obscured the central issue.) – kjo Feb 08 '14 at 19:28
  • 1
    Thanks, this at least did answer *my* question; it lets me use my Git client to revert all of the "changes" (reversions from the new upstream version) I don't want to keep, and I can still keep the local changes I do. – Nicholas Riley Sep 26 '15 at 01:07
2

OK, after running into something like this:

% git stash pop
Auto-merging foo
CONFLICT (content): Merge conflict in foo
Auto-merging bar
CONFLICT (content): Merge conflict in bar
Auto-merging baz
CONFLICT (content): Merge conflict in baz
...

# $#@?!?!%$!*@#...

...the best solution that I've managed to come up with is to respond with this:

% git checkout --theirs $(git diff --name-only --diff-filter=U)
% git reset
% git stash drop

(Based on this answer.)

Community
  • 1
  • 1
kjo
  • 27,601
  • 42
  • 124
  • 225
  • 1
    Why even bother with stash at that point? You are just dropping all of your changes. – Jonathan Wren May 08 '13 at 22:05
  • No I'm not. I've tested it. The above is fine. – kjo May 08 '13 at 22:21
  • Sorry, by "your" I meant the changes from the server, not your stashed changes. That was poorly worded. – Jonathan Wren May 09 '13 at 21:58
  • @Duotrigesimal: well your wording could not have been worse than the wording of my original question! See my latest comment to [jthill](http://stackoverflow.com/users/1290731/jthill)'s [answer](http://stackoverflow.com/a/16450765/559827), and references therein. – kjo Feb 08 '14 at 19:32
  • +1 this worked for me where i had several in process items scattered about in my dirty stage, stashed it, and then later needed to see what all i had been doing. two notes: i did not drop my stash...that's not relevant to seeing `git diff` without the auto merge nonsense. also, `git checkout --theirs $(git diff --name-only --diff-filter=U)` needs to be run from the top of the git repo (it gave file path errors for the changed files when i ran it from a sub directory). – WEBjuju Dec 04 '17 at 18:24
2

The fact that you are seeing a merge conflict means that there was a change in the foo file that you pulled from the server. So, copying your file over and moving back will completely nuke all changes from the repo in the foo file. If you commit that, the other person that committed changes to foo will hate you.

The answer to your question depends on what you are trying to accomplish. Are you trying to see what the changes on the server were compared to your code? Are you trying to avoid dealing with other people's code until you are done?

If you just want to see what the change to the branch were, you can use git fetch instead of git pull and compare your current code with that. Or, if you don't want to merge your changes now, consider working in a separate branch, or not pulling until you are ready to merge.

Jonathan Wren
  • 3,556
  • 21
  • 28
  • I don't understand your question, since I had already posted a solution that spelled unambiguously what I was "trying to accomplish". – kjo May 09 '13 at 20:32
  • @kjo The reason I'm not sure what you're trying to accomplish is that the process you are describing (both in your original post, and in your solution) will cause you to undo other's changes in your file without reviewing them. Which, if you were completely aware of your process might be ok. But your description saying that your process is the "moral equivalent" of the stash command shows that you are not quite aware of the damage your process will cause. – Jonathan Wren May 09 '13 at 21:54
  • @kjo There could be any number of changes in the file on the server from your version. If a single one of those changes conflicts, rather than resolving the conflict, you are discarding all changes in the file in favor of your potentially outdated version of the file. This could cause a lot of headaches down the road as fixes people thought were implemented are, all of a sudden, not in the latest version. – Jonathan Wren May 09 '13 at 21:57
  • sorry, but I don't know how to make my intention clearer than by the solution I posted: that *is* what I am after; perhaps the exchange in the comments to http://stackoverflow.com/questions/16449830/#16450047 will clarify matters further. – kjo May 11 '13 at 17:36
  • @kjo If that's what you're after, that's fine. But you are aware that you are deleting changes, right? – Jonathan Wren May 11 '13 at 19:05