Search code examples
gitmercurialpushdvcs

Does git force you to pull changes to the tip? Does your push include those files?


With Mercurial, if you try and push and there were commits made to the main repo, you are forced to pull and merge.

The problem is, when you go to commit your changes, the changeset you push contains files that you were forced to merge.

You didn't touch these files, yet the history shows you modifying them.

Does git have this same behaviour? Or is it smart enough to know you didn't modify those files so there is no point in having it your history.


Solution

  • With Mercurial, if you try and push and there were commits made to the main repo, you are forced to pull and merge.

    Both Git and Mercurial work essentially the same here. When new commits have been made in the remote repo, git push will abort with:

    $ git push
    To /home/mg/tmp/git/repo-1
     ! [rejected]        master -> master (non-fast-forward)
    error: failed to push some refs to '/home/mg/tmp/git/repo-1'
    To prevent you from losing history, non-fast-forward updates were rejected
    Merge the remote changes (e.g. 'git pull') before pushing again.  See the
    'Note about fast-forwards' section of 'git push --help' for details.
    

    Mercurial also aborts in this situation:

    $ hg push
    pushing to /home/mg/tmp/hg/repo-1
    searching for changes
    abort: push creates new remote head 436b0ce25d42!
    (you should pull and merge or use push -f to force)
    

    So far so good. In Git, you'll now run git pull and in Mercurial you can run hg fetch. This will retrieve and merge the new changesets and works the same in both systems.

    The problem is, when you go to commit your changes, the changeset you push contains files that you were forced to merge.

    You didn't touch these files, yet the history shows you modifying them.

    I think you're misunderstanding what Mercurial is showing you. Both systems work on snapshots of the full repository so both systems will force you to push changes to files you didn't touch in the merge. A Git merge commit will also "contain" all the files that wasn't changed — they're referenced through the tree object referenced by the commit.

    Because both systems use snapshots, the question of "who modified what?" only makes sense when you compare two snapshots. For a merge changeset, there are two interesting comparisons: against the first parent and against the second parent.

    The difference between Mercurial and Git is that no patch is shown for a merge changeset:

    $ git log -p -1
    commit 8cdbbef1b6f0fc8e01997f6842b7f249d066a30c
    Merge: b77058e e99e303
    Author: Martin Geisler <[email protected]>
    Date:   Mon Jan 23 14:09:09 2012 +0100
    
        Merge branch 'master' of /home/mg/tmp/git/repo-1
    

    whereas Mercurial always shows a patch against the first parent:

    $ hg log -p -l 1
    changeset:   3:94060588f0a5
    tag:         tip
    parent:      2:06d00ad5063b
    parent:      1:436b0ce25d42
    user:        Martin Geisler <[email protected]>
    date:        Mon Jan 23 14:01:25 2012 +0100
    summary:     Automated merge with file:///home/mg/tmp/hg/repo-1
    
    diff --git a/b b/b
    new file mode 100644
    --- /dev/null
    +++ b/b
    @@ -0,0 +1,1 @@
    +b
    

    This is shows that the file b was not present in the first parent of the merge. In other words: it probably came from the second parent. Use hg diff -r "p2(3):3" or hg diff -r "3^2:3" to check this. In Git you'll use git diff "8cdbbef^2" 8cdbbef to compare with the second parent.

    Notice that both systems show the same for git log b and hg log b: the merge changeset is not in the log for the file unless the file was actually modified as part of the merge changeset. Files are typically modified in merge changesets because conflicts need to be resolved before the merge is committed.