mercurial/localrepo.py
changeset 10926 4d81cbd8a851
parent 10920 39c69b5dc258
parent 10925 a101a743c570
child 10960 ca739acf1a98
--- a/mercurial/localrepo.py	Thu Apr 15 20:25:26 2010 +0200
+++ b/mercurial/localrepo.py	Thu Apr 15 22:34:26 2010 +0200
@@ -1514,110 +1514,106 @@
         remote_heads = remote.heads()
         inc = self.findincoming(remote, common, remote_heads, force=force)
 
+        cl = self.changelog
         update, updated_heads = self.findoutgoing(remote, common, remote_heads)
-        msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
-
-        def checkbranch(lheads, rheads, lheadcnt, branchname=None):
-            '''
-            check whether there are more local heads than remote heads on
-            a specific branch.
-
-            lheads: local branch heads
-            rheads: remote branch heads
-            lheadcnt: total number of local branch heads
-            '''
-
-            warn = 0
-
-            if len(lheads) > len(rheads):
-                warn = 1
-            else:
-                # add local heads involved in the push
-                updatelheads = [self.changelog.heads(x, lheads)
-                                for x in update]
-                newheads = set(sum(updatelheads, [])) & set(lheads)
-
-                if not newheads:
-                    return True
-
-                # add heads we don't have or that are not involved in the push
-                for r in rheads:
-                    if r in self.changelog.nodemap:
-                        desc = self.changelog.heads(r, heads)
-                        l = [h for h in heads if h in desc]
-                        if not l:
-                            newheads.add(r)
-                    else:
-                        newheads.add(r)
-                if len(newheads) > len(rheads):
-                    warn = 1
-
-            if warn:
-                if branchname is not None:
-                    msg = _("abort: push creates new remote heads"
-                            " on branch '%s'!\n") % branchname
-                else:
-                    msg = _("abort: push creates new remote heads!\n")
-                self.ui.warn(msg)
-                if lheadcnt > len(rheads):
-                    self.ui.status(_("(did you forget to merge?"
-                                     " use push -f to force)\n"))
-                else:
-                    self.ui.status(_("(you should pull and merge or"
-                                     " use push -f to force)\n"))
-                return False
-            return True
+        outg, bases, heads = cl.nodesbetween(update, revs)
 
         if not bases:
             self.ui.status(_("no changes found\n"))
             return None, 1
-        elif not force:
-            # Check for each named branch if we're creating new remote heads.
-            # To be a remote head after push, node must be either:
-            # - unknown locally
-            # - a local outgoing head descended from update
-            # - a remote head that's known locally and not
-            #   ancestral to an outgoing head
-            #
-            # New named branches cannot be created without --force.
+
+        if not force and remote_heads != [nullid]:
+
+            def fail_multiple_heads(unsynced, branch=None):
+                if branch:
+                    msg = _("abort: push creates new remote heads"
+                            " on branch '%s'!\n") % branch
+                else:
+                    msg = _("abort: push creates new remote heads!\n")
+                self.ui.warn(msg)
+                if unsynced:
+                    self.ui.status(_("(you should pull and merge or"
+                                     " use push -f to force)\n"))
+                else:
+                    self.ui.status(_("(did you forget to merge?"
+                                     " use push -f to force)\n"))
+                return None, 0
 
-            if remote_heads != [nullid]:
-                if remote.capable('branchmap'):
-                    remotebrheads = remote.branchmap()
+            if remote.capable('branchmap'):
+                # Check for each named branch if we're creating new remote heads.
+                # To be a remote head after push, node must be either:
+                # - unknown locally
+                # - a local outgoing head descended from update
+                # - a remote head that's known locally and not
+                #   ancestral to an outgoing head
+                #
+                # New named branches cannot be created without --force.
+
+                # 1. Create set of branches involved in the push.
+                branches = set(self[n].branch() for n in outg)
+
+                # 2. Check for new branches on the remote.
+                remotemap = remote.branchmap()
+                newbranches = branches - set(remotemap)
+                if newbranches: # new branch requires --force
+                    branchnames = ', '.join("%s" % b for b in newbranches)
+                    self.ui.warn(_("abort: push creates "
+                                   "new remote branches: %s!\n")
+                                 % branchnames)
+                    self.ui.status(_("(use 'hg push -f' to force)\n"))
+                    return None, 0
 
-                    lbrmap = self.branchmap()
-                    localbrheads = {}
-                    if not revs:
-                        for br, hds in lbrmap.iteritems():
-                            localbrheads[br] = (len(hds), hds)
-                    else:
-                        ctxgen = (self[n] for n in msng_cl)
-                        self._updatebranchcache(localbrheads, ctxgen)
-                        for br, hds in localbrheads.iteritems():
-                            localbrheads[br] = (len(lbrmap[br]), hds)
+                # 3. Construct the initial oldmap and newmap dicts.
+                # They contain information about the remote heads before and
+                # after the push, respectively.
+                # Heads not found locally are not included in either dict,
+                # since they won't be affected by the push.
+                # unsynced contains all branches with incoming changesets.
+                oldmap = {}
+                newmap = {}
+                unsynced = set()
+                for branch in branches:
+                    remoteheads = remotemap[branch]
+                    prunedheads = [h for h in remoteheads if h in cl.nodemap]
+                    oldmap[branch] = prunedheads
+                    newmap[branch] = list(prunedheads)
+                    if len(remoteheads) > len(prunedheads):
+                        unsynced.add(branch)
+
+                # 4. Update newmap with outgoing changes.
+                # This will possibly add new heads and remove existing ones.
+                ctxgen = (self[n] for n in outg)
+                self._updatebranchcache(newmap, ctxgen)
 
-                    newbranches = list(set(localbrheads) - set(remotebrheads))
-                    if newbranches: # new branch requires --force
-                        branchnames = ', '.join("%s" % b for b in newbranches)
-                        self.ui.warn(_("abort: push creates "
-                                       "new remote branches: %s!\n")
-                                     % branchnames)
-                        # propose 'push -b .' in the msg too?
-                        self.ui.status(_("(use 'hg push -f' to force)\n"))
-                        return None, 0
-                    for branch, x in localbrheads.iteritems():
-                        if branch in remotebrheads:
-                            headcnt, lheads = x
-                            rheads = remotebrheads[branch]
-                            if not checkbranch(lheads, rheads, headcnt, branch):
-                                return None, 0
-                else:
-                    if not checkbranch(heads, remote_heads, len(heads)):
-                        return None, 0
+                # 5. Check for new heads.
+                # If there are more heads after the push than before, a suitable
+                # warning, depending on unsynced status, is displayed.
+                for branch in branches:
+                    if len(newmap[branch]) > len(oldmap[branch]):
+                        return fail_multiple_heads(branch in unsynced, branch)
+
+                # 6. Check for unsynced changes on involved branches.
+                if unsynced:
+                    self.ui.warn(_("note: unsynced remote changes!\n"))
 
-            if inc:
-                self.ui.warn(_("note: unsynced remote changes!\n"))
-
+            else:
+                # Old servers: Check for new topological heads.
+                # Code based on _updatebranchcache.
+                newheads = set(h for h in remote_heads if h in cl.nodemap)
+                oldheadcnt = len(newheads)
+                newheads.update(outg)
+                if len(newheads) > 1:
+                    for latest in reversed(outg):
+                        if latest not in newheads:
+                            continue
+                        minhrev = min(cl.rev(h) for h in newheads)
+                        reachable = cl.reachable(latest, cl.node(minhrev))
+                        reachable.remove(latest)
+                        newheads.difference_update(reachable)
+                if len(newheads) > oldheadcnt:
+                    return fail_multiple_heads(inc)
+                if inc:
+                    self.ui.warn(_("note: unsynced remote changes!\n"))
 
         if revs is None:
             # use the fast path, no race possible on push