# HG changeset patch # User Pierre-Yves David # Date 2012-01-19 14:50:55 # Node ID 4154338f0bc04502a65b11cdcb5d68a793d29ed7 # Parent 44b5de2d18768043d5a4e12ef8bc62f372dbbd60 discovery: diet discovery.prepush from non-discovery code The ``discovery.prepush`` function was doing multiple things not related to discovery. This changeset move some code into the ``localrepo.push`` method. The old ``discovery.prepush`` function jobs is now restricted to checking for multple head creation. It was then renamed ``discovery.checkheads``. This new ``discovery.checkheads`` function may receive several other changes in the future but we are a bit too much near the freeze for a wider refactoring. diff --git a/mercurial/discovery.py b/mercurial/discovery.py --- a/mercurial/discovery.py +++ b/mercurial/discovery.py @@ -132,146 +132,108 @@ def findcommonoutgoing(repo, other, only return og -def prepush(repo, remote, force, revs, newbranch): - '''Analyze the local and remote repositories and determine which - changesets need to be pushed to the remote. Return value depends - on circumstances: - - If we are not going to push anything, return a tuple (None, 1, - common) The third element "common" is the list of heads of the - common set between local and remote. +def checkheads(repo, remote, outgoing, remoteheads, newbranch=False): + """Check that a push won't add any outgoing head - Otherwise, return a tuple (changegroup, remoteheads, futureheads), - where changegroup is a readable file-like object whose read() - returns successive changegroup chunks ready to be sent over the - wire, remoteheads is the list of remote heads and futureheads is - the list of heads of the common set between local and remote to - be after push completion. - ''' - commoninc = findcommonincoming(repo, remote, force=force) - outgoing = findcommonoutgoing(repo, remote, onlyheads=revs, - commoninc=commoninc, force=force) - _common, inc, remoteheads = commoninc + raise Abort error and display ui message as needed. + """ + if remoteheads == [nullid]: + # remote is empty, nothing to check. + return cl = repo.changelog - outg = outgoing.missing - common = outgoing.commonheads + 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 - if not outg: - if outgoing.excluded: - repo.ui.status(_("no changes to push but %i secret changesets\n") - % len(outgoing.excluded)) - else: - repo.ui.status(_("no changes found\n")) - return None, 1, common + # 1. Create set of branches involved in the push. + branches = set(repo[n].branch() for n in outgoing.missing) - if not force and remoteheads != [nullid]: - 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 - - # 1. Create set of branches involved in the push. - branches = set(repo[n].branch() for n in outg) + # 2. Check for new branches on the remote. + remotemap = remote.branchmap() + newbranches = branches - set(remotemap) + if newbranches and not newbranch: # new branch requires --new-branch + branchnames = ', '.join(sorted(newbranches)) + raise util.Abort(_("push creates new remote branches: %s!") + % branchnames, + hint=_("use 'hg push --new-branch' to create" + " new remote branches")) + branches.difference_update(newbranches) - # 2. Check for new branches on the remote. - remotemap = remote.branchmap() - newbranches = branches - set(remotemap) - if newbranches and not newbranch: # new branch requires --new-branch - branchnames = ', '.join(sorted(newbranches)) - raise util.Abort(_("push creates new remote branches: %s!") - % branchnames, - hint=_("use 'hg push --new-branch' to create" - " new remote branches")) - branches.difference_update(newbranches) + # 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: + remotebrheads = remotemap[branch] + prunedbrheads = [h for h in remotebrheads if h in cl.nodemap] + oldmap[branch] = prunedbrheads + newmap[branch] = list(prunedbrheads) + if len(remotebrheads) > len(prunedbrheads): + unsynced.add(branch) - # 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: - remotebrheads = remotemap[branch] - prunedbrheads = [h for h in remotebrheads if h in cl.nodemap] - oldmap[branch] = prunedbrheads - newmap[branch] = list(prunedbrheads) - if len(remotebrheads) > len(prunedbrheads): - unsynced.add(branch) - - # 4. Update newmap with outgoing changes. - # This will possibly add new heads and remove existing ones. - ctxgen = (repo[n] for n in outg) - repo._updatebranchcache(newmap, ctxgen) + # 4. Update newmap with outgoing changes. + # This will possibly add new heads and remove existing ones. + ctxgen = (repo[n] for n in outgoing.missing) + repo._updatebranchcache(newmap, ctxgen) - else: - # 1-4b. old servers: Check for new topological heads. - # Construct {old,new}map with branch = None (topological branch). - # (code based on _updatebranchcache) - oldheads = set(h for h in remoteheads if h in cl.nodemap) - newheads = oldheads.union(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) - branches = set([None]) - newmap = {None: newheads} - oldmap = {None: oldheads} - unsynced = inc and branches or set() + else: + # 1-4b. old servers: Check for new topological heads. + # Construct {old,new}map with branch = None (topological branch). + # (code based on _updatebranchcache) + oldheads = set(h for h in remoteheads if h in cl.nodemap) + newheads = oldheads.union(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) + branches = set([None]) + newmap = {None: newheads} + oldmap = {None: oldheads} + unsynced = inc and branches or set() - # 5. Check for new heads. - # If there are more heads after the push than before, a suitable - # error message, depending on unsynced status, is displayed. - error = None - for branch in branches: - newhs = set(newmap[branch]) - oldhs = set(oldmap[branch]) - if len(newhs) > len(oldhs): - dhs = list(newhs - oldhs) - if error is None: - if branch not in ('default', None): - error = _("push creates new remote head %s " - "on branch '%s'!") % (short(dhs[0]), branch) - else: - error = _("push creates new remote head %s!" - ) % short(dhs[0]) - if branch in unsynced: - hint = _("you should pull and merge or " - "use push -f to force") - else: - hint = _("did you forget to merge? " - "use push -f to force") - if branch is not None: - repo.ui.note(_("new remote heads on branch '%s'\n") % branch) - for h in dhs: - repo.ui.note(_("new remote head %s\n") % short(h)) - if error: - raise util.Abort(error, hint=hint) + # 5. Check for new heads. + # If there are more heads after the push than before, a suitable + # error message, depending on unsynced status, is displayed. + error = None + for branch in branches: + newhs = set(newmap[branch]) + oldhs = set(oldmap[branch]) + if len(newhs) > len(oldhs): + dhs = list(newhs - oldhs) + if error is None: + if branch not in ('default', None): + error = _("push creates new remote head %s " + "on branch '%s'!") % (short(dhs[0]), branch) + else: + error = _("push creates new remote head %s!" + ) % short(dhs[0]) + if branch in unsynced: + hint = _("you should pull and merge or " + "use push -f to force") + else: + hint = _("did you forget to merge? " + "use push -f to force") + if branch is not None: + repo.ui.note(_("new remote heads on branch '%s'\n") % branch) + for h in dhs: + repo.ui.note(_("new remote head %s\n") % short(h)) + if error: + raise util.Abort(error, hint=hint) - # 6. Check for unsynced changes on involved branches. - if unsynced: - repo.ui.warn(_("note: unsynced remote changes!\n")) - - if revs is None and not outgoing.excluded: - # push everything, - # use the fast path, no race possible on push - cg = repo._changegroup(outg, 'push') - else: - cg = repo.getlocalbundle('push', outgoing) - # no need to compute outg ancestor. All node in outg have either: - # - parents in outg - # - parents in common - # - nullid parent - rset = repo.set('heads(%ln + %ln)', common, outg) - futureheads = [ctx.node() for ctx in rset] - return cg, remoteheads, futureheads + # 6. Check for unsynced changes on involved branches. + if unsynced: + repo.ui.warn(_("note: unsynced remote changes!\n")) diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -1606,26 +1606,59 @@ class localrepository(repo.repository): # get local lock as we might write phase data locallock = self.lock() try: - cg, remote_heads, fut = discovery.prepush(self, remote, force, - revs, newbranch) - ret = remote_heads - # create a callback for addchangegroup. - # If will be used branch of the conditionnal too. - if cg is not None: + # discovery + fci = discovery.findcommonincoming + commoninc = fci(self, remote, force=force) + common, inc, remoteheads = commoninc + fco = discovery.findcommonoutgoing + outgoing = fco(self, remote, onlyheads=revs, + commoninc=commoninc, force=force) + + + if not outgoing.missing: + # nothing to push + if outgoing.excluded: + msg = "no changes to push but %i secret changesets\n" + self.ui.status(_(msg) % len(outgoing.excluded)) + else: + self.ui.status(_("no changes found\n")) + fut = outgoing.common + ret = 1 + else: + # something to push + if not force: + discovery.checkheads(self, remote, outgoing, + remoteheads, newbranch) + + # create a changegroup from local + if revs is None and not outgoing.excluded: + # push everything, + # use the fast path, no race possible on push + cg = self._changegroup(outgoing.missing, 'push') + else: + cg = self.getlocalbundle('push', outgoing) + + # apply changegroup to remote if unbundle: # local repo finds heads on server, finds out what # revs it must push. once revs transferred, if server # finds it has different heads (someone else won # commit/push race), server aborts. if force: - remote_heads = ['force'] + remoteheads = ['force'] # ssh: return remote's addchangegroup() # http: return remote's addchangegroup() or 0 for error - ret = remote.unbundle(cg, remote_heads, 'push') + ret = remote.unbundle(cg, remoteheads, 'push') else: # we return an integer indicating remote head count change ret = remote.addchangegroup(cg, 'push', self.url()) + # compute what should be the now common + # + # XXX If push failed we should use strict common and not + # future to avoid pushing phase data on unknown changeset. + # This is to done later. + fut = outgoing.commonheads + outgoing.missingheads # even when we don't push, exchanging phase data is useful remotephases = remote.listkeys('phases') if not remotephases: # old server or public only repo @@ -1641,10 +1674,6 @@ class localrepository(repo.repository): phases.advanceboundary(self, phases.public, pheads) phases.advanceboundary(self, phases.draft, fut) ### Apply local phase on remote - # - # XXX If push failed we should use strict common and not - # future to avoid pushing phase data on unknown changeset. - # This is to done later. # Get the list of all revs draft on remote by public here. # XXX Beware that revset break if droots is not strictly diff --git a/tests/test-check-code-hg.t b/tests/test-check-code-hg.t --- a/tests/test-check-code-hg.t +++ b/tests/test-check-code-hg.t @@ -438,9 +438,6 @@ > if not st is None and not getkind(st.st_mode) in (regkind, lnkkind): warning: line over 80 characters mercurial/discovery.py:0: - > repo.ui.note(_("new remote heads on branch '%s'\n") % branch) - warning: line over 80 characters - mercurial/discovery.py:0: > If onlyheads is given, only nodes ancestral to nodes in onlyheads (inclusive) warning: line over 80 characters mercurial/discovery.py:0: