Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I agree that the author should've been committing far more regularly than he apparently does. In addition, "git pull" is a bad idea (you never know what nastiness someone else might have committed — "git fetch" + "git merge" is a far saner way to stay up-to-date).

That said, hats off to the author for tracking down the problem. Regardless of workflow flaws, the behavior he observed is a bug, and I'm glad it's fixed.



Can you elaborate? A lot of beginner level material explains "pull" as a combination of "fetch" + "merge." What makes the latter saner?


By doing a fetch first, and a merge in a separate operation, you're at least presenting yourself with an opportunity to check the code that you are merging.

With `git pull`, it is all done in one operation.

Of course, if you've already reviewed the code or are pulling from your own remote repository then `git pull` is likely fine.


Agreed. After a fetch, I always review the changes on the remote branch. Depending on the circumstances, I can then make an informed decision about my own code, with the following possible outcomes:

1. I might choose not to merge. The upstream code may be bad, or it may not be ready for a merge. I may need to do some more work to prepare my own code to merge.

2. I might wish to perform a fast-forward merge (if one is possible).

3. I might wish to force a non-fast-forward merge (if a fast-forward is possible). This is often a good idea, as it helps keep groups of commits related to a particular feature isolated.

4. I might wish to rebase.


I treat pulls as a sync and always rebase local changes before pushing them. Merge pulls make for a very messy and verbose history.


When calling fetch and then merge (or using rebase), you have more control over how the other changes impact your commits. Just pulling will result in a merge commit that contains all of the changes between the point you last pulled. This becomes confusing because it will look like you are adding files or making changes, when you are actually not.

The reason this happens is because git uses directed graphs and there is no link between the point at which you diverged from master and the other changes came in. The merge commit creates this. Unfortunately, it looks like you just committed everything that changed on both branches and the history can be difficult to track and/or confusing.

Fetch then merge will make the history more clear, but you still create a merge commit that will include all of the file changes (correct me if I am wrong here). git pull --rebase will take your changes out of the equation, pull in the new history and then replay them against the updated master branch. This is nice because your commit will only include (at this point) any merge conflicts resulting from your changes.

For a better explanation, with diagrams, see http://progit.org/book/ch3-6.html


> Unfortunately, it looks like you just committed everything that changed on both branches and the history can be difficult to track and/or confusing.

I don't think this is really true. You'll just introduce a single merge commit on top of both your commits and the other person's. Yes, if you diff that merge against your own work, you'll see all the work that other people committed, but I think it's fairly well understood that a merge commit is just that - you merging your changes with other changes.

It can be a bit tricky to read the log when merge commits are involved, but try the --graph option or a graphical log tool.


fetch is a fairly straightforward operation with predictable (i.e. get me all the objects and refs in the remote repo) but 'merge' actually moves the branch(es) in your local repo around.

After you do a fetch you can go on to do a merge just as though you did a git pull, but if you break down the steps then you now have the option to do a rebase or other operation as you see fit.


Do the fetch, diff against remote/branch and see what changed, look at the commit logs, and then merge.


Sure, I would agree with that, but in this case I already knew what the one commit I was pulling did. And the fetch,diff, merge wouldn't have helped. The file that was destroyed wasn't changed in the commit that was fetched.


Running

  git pull --rebase
is a good practice. That way you can abort and roll back if there are any serious conflicts.


Be careful with this though: if you have a merge ready to push, and you pull with rebase, your merge will be 'flattened' and all commits in it duplicated on your current branch. I've been bitten by this a couple of times.


Only if you actually want to rebase! While rebasing is great, it rewrites history and should not be done lightly.


It rewinds your work, fast forwards to the fetched branch and applies your work on top of it. It doesn't create a merge commit, which can pollute your tree unnecessarily.


I believe you're describing "git rebase". e.g.

    git fetch
    git rebase origin/master
instead of:

    git pull
which you can also do with:

    git pull --rebase
But really, the preferred way is to use topic branches. So if you're on branch "foo", this is how you integrate into master.

    git checkout master # because it always matches upstream
    git pull            # this will always fast-forward
    git merge foo
    git push
That does create a merge commit but it does it in the right direction (master 1st parent, topic branch 2nd). You get to see the parallel development which is good to preserve in the history. Otherwise, rebasing is nice because it keeps the history linear.


Yeah, you're right. I must have misread the question.

Didn't know about `git pull --rebase`. Thanks for that.


Just to point out that fetch + merge would have caused the same problem. In this case it was a small team, with advance knowledge of what the commit was, so no need to inspect.

But, in any case doing the fetch would have shown that the file was not modified in the fetched tree, and it would have gone ahead and overwritten my changes (without even listing in the merge log that the file was changed).

FWIW, I agree that fetch + (merge | rebase) is in general the best way to go, but I think there is a case when you a pulling in a simple fix from head where doing a pull is legitimate.

After all the documentation says this is a safe operation:

"If any of the remote changes overlap with local uncommitted changes, the merge will be automatically cancelled and the work tree untouched" (from git pull --help).

If this isn't meant to be considered a safe operation git pull should abort if there are any changes to the working directory.


"Safe" or otherwise, it's (IMO) never a good idea to merge uncommitted files.

You're losing history that way: If the merge doesn't actually work, then you've got a screwed up file and no way to roll it back.

It's one of the great strengths of git that you can commit files even if someone else has changed them. It's a bad idea to merge when you have anything significant checked out (I'll leave temporary debugging changes checked out, or very small changes, but that's it). Heck, it's a good idea to check in every few hours, to track changes your making.


Agree 100% I've seen this same type of issue occur in mercurial when people were doing what I call "all or nothing merging". You should not be merging unless you can get to the precise pre-merge state.


the real "mistake" here was not doing 'pull' it was doing 'pull' while having uncommitted changes in the working directory. I'd commit or stash before the pull/


I would have sworn that git would refuse to do git pull with uncommitted local changes.


It won't let you pull if checked-in files have been modified, but it will let you pull if you have uncommitted new files.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: