On Sun, Mar 18, 2007 at 06:31:21PM +0100, Matthieu Moy wrote: > I have a repository with a working tree on a machine A, did a clone to > another machine B and commited there locally. > > I want my changes to get back into the first repository, so I did a > "push". The new commit is in the history, I can see it with "git log", > but the modifications are not in the working tree. The general answer (which you've already received) is to tell folks is to simply don't use "git push" to remote trees; basically, if you ever have a non-bare repository, it doesn't do what you expect, and it will leave the novice user horribly confused. A much better answer is to simply go back to machine A, and pull from machine B. I was exploring though to see if there was anything we could do better, and so I used my standard test repository of the GNU Hello, world program, and did the following: git clone hello r1 git clone r1 r2 cd r1 <edit hello.c's headers to be GPL v2 only> git commit -a -m "GPL v2 only" cd ../r2 <edit hello.c so that the message printed is "Hello, world!" instead of "hello, world"> cd .. OK, so this sets up the standard test setup of repositories r1 and r2. r1 contains a committed change so that hello.c is GPLv2 only. r2 contains an uncommitted change to the actual text printed by hello.c. The changes are nicely seprated in distaince by over 100 lines, so there should be no problems with merges. Let's play... Experiment #1. Let's try pushing from r1 to r2. cd r1 git push ../r2 This pushes the change GPLv2 change from r1 to r2. However, it leaves the working tree and the index untouched, which leads to some very unexpected and surprising behavior: a) If you do a "git commit" you will commit the current contents of the index, which is usually the contents of the head of r2 before the push. b) If you do a "git commit -a" you will commit the modified changes to the working directory --- based off of the state of r2 before the push. What will therefore show up in the revision log is something which appears to be based off of the more recent change in r1, but which is really based off of the old history as of r2 before the push. All of this is bad, which is why "git push" to a non-bare repository is extremely surprising. (As an aside, what Bitkeeper would do is to update the working tree, but it added the constraint that it would only allow the "bk push" if the push resulted in a fast-forward merge, thus guaranteeing no conflicts, and if the none of the files that would need to be updated in the working tree had been locally modified; if either constraint were modified, the push would be aborted, and the remote repository not modified at all. It would be nice if we could enforce these constraints using the appropriate hooks, but from what I can tell the hooks aren't in the right place for us to be able to do that, or to be able to undo or recover from a bad push. More on that in a bit.) OK, so suppose we do the push anyway. As you suggested, one possible solution is: > $ git reset --soft <commit-id-before-the-push> > $ git merge <commit-id-after-the-push> > > But it means I have to remember <commit-id-before-the-push>. Is it at all possible to figure out <commit-id-before-the-push>? It seems the answer is no, and I suspect that's a bug. Maybe it doesn't make sense to save the original HEAD in ORIG_HEAD in r2, but surely the original HEAD should be saved in the reflog, right? Well, at the moment it saves it in neither case. The problem is without doing this, it is as far as I can tell impossible to determine what revision the index is currently corresponding to, and without this information, it's very limited in what you can do. What possible solution which you *can* do is: git diff > /tmp/stage-patch git reset --hard patch -p1 < /tmp/stage-patch But that seems a bit manual and somewhat kludgy. If we had the revision of r2 before the push, it would be possible to do a 3-way merge, which in some cases might result in a cleaner merge of the modified files in the working tree. Experiment #2. Let's try pulling from r2 to r1 So this is what we tell people they should do; so how well does it work in this case? <do the above experimental setup> cd r2 git pull ../r1 What do we get? Updating f2e3cc0..37508dc hello.c: needs update fatal: Entry 'hello.c' not uptodate. Cannot merge. hello.c | 7 +++---- 1 files changed, 3 insertions(+), 4 deletions(-) Oh, dear. Since hello.c was locally modified, git-merge refused to do a 3-way merge to the local file. That's unfortunate, since if it had, it would have succeeded, and in this case it would have done the right thing. git-checkout will do something similar, but it at has an -m option which will do a 3-way merge to update the local working file. Unfortunately git-merge and git-pull do not have such an option. 'twould be nice if it did, but that's going to have to wait someone wanting to scratch that particular itch... The failure leaves the index and the working tree in the same confused state as the "git push" scenario, though --- the index is still referring to original state of the tree before the pull, but the HEAD has been updated to after the pull, so "git commit" will lead to the same confusing behavior. Fortunately, though, when we do a pull, ORIG_HEAD and the reflog are updated, so we can get back to our original state via a command like this: git update-ref HEAD ORIG_HEAD So ok, let's break down the git pull into its two constiuent parts. The git-fetch and the git-merge. git fetch git merge FETCH_HEAD This fails in the same way: Updating f2e3cc0..37508dc hello.c: needs update fatal: Entry 'hello.c' not uptodate. Cannot merge. hello.c | 7 +++---- 1 files changed, 3 insertions(+), 4 deletions(-) And just as before, it leaves HEAD updated to FETCH_HEAD, even though it failed. So it's consistent, but that's not what the documentation for git-merge states: You may have local modifications in the working tree files. In other words, git-diff is allowed to report changes. However, the merge uses your working tree as the working area, and in order to prevent the merge operation from losing such changes, it makes sure that they do not interfere with the merge. Those complex tables in read-tree docu- mentation define what it means for a path to "interfere with the merge". And if your local modifications interfere with the merge, again, it stops before touching anything. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Well, it certainly stops before touching any local files, but it has already updated HEAD, which leaves things in a very confusion situation. Maybe it would be better if git-merge atomically failed and left HEAD back pointing at the original revision? - Ted - To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html