discovery.py
202 lines
| 8.8 KiB
| text/x-python
|
PythonLexer
/ mercurial / discovery.py
Dirkjan Ochtman
|
r11313 | # discovery.py - protocol changeset discovery functions | ||
Dirkjan Ochtman
|
r11301 | # | ||
Dirkjan Ochtman
|
r11313 | # Copyright 2010 Matt Mackall <mpm@selenic.com> | ||
Dirkjan Ochtman
|
r11301 | # | ||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
from node import nullid, short | ||||
from i18n import _ | ||||
Sune Foldager
|
r14184 | import util, setdiscovery, treediscovery | ||
Dirkjan Ochtman
|
r11301 | |||
Peter Arrenbrecht
|
r14073 | def findcommonincoming(repo, remote, heads=None, force=False): | ||
"""Return a tuple (common, anyincoming, heads) used to identify the common | ||||
subset of nodes between repo and remote. | ||||
Dirkjan Ochtman
|
r11301 | |||
Peter Arrenbrecht
|
r14073 | "common" is a list of (at least) the heads of the common subset. | ||
"anyincoming" is testable as a boolean indicating if any nodes are missing | ||||
locally. If remote does not support getbundle, this actually is a list of | ||||
roots of the nodes that would be incoming, to be supplied to | ||||
changegroupsubset. No code except for pull should be relying on this fact | ||||
any longer. | ||||
"heads" is either the supplied heads, or else the remote's heads. | ||||
Peter Arrenbrecht
|
r14164 | |||
If you pass heads and they are all known locally, the reponse lists justs | ||||
these heads in "common" and in "heads". | ||||
Peter Arrenbrecht
|
r14213 | |||
Please use findcommonoutgoing to compute the set of outgoing nodes to give | ||||
extensions a good hook into outgoing. | ||||
Dirkjan Ochtman
|
r11301 | """ | ||
Peter Arrenbrecht
|
r14073 | |||
Peter Arrenbrecht
|
r14164 | if not remote.capable('getbundle'): | ||
return treediscovery.findcommonincoming(repo, remote, heads, force) | ||||
Dirkjan Ochtman
|
r11301 | |||
Peter Arrenbrecht
|
r14164 | if heads: | ||
allknown = True | ||||
nm = repo.changelog.nodemap | ||||
for h in heads: | ||||
if nm.get(h) is None: | ||||
allknown = False | ||||
break | ||||
if allknown: | ||||
return (heads, False, heads) | ||||
Dirkjan Ochtman
|
r11301 | |||
Peter Arrenbrecht
|
r14164 | res = setdiscovery.findcommonheads(repo.ui, repo, remote, | ||
abortwhenunrelated=not force) | ||||
common, anyinc, srvheads = res | ||||
return (list(common), anyinc, heads or list(srvheads)) | ||||
Dirkjan Ochtman
|
r11301 | |||
Peter Arrenbrecht
|
r14213 | def findcommonoutgoing(repo, other, onlyheads=None, force=False, commoninc=None): | ||
'''Return a tuple (common, anyoutgoing, heads) used to identify the set | ||||
of nodes present in repo but not in other. | ||||
If onlyheads is given, only nodes ancestral to nodes in onlyheads (inclusive) | ||||
are included. If you already know the local repo's heads, passing them in | ||||
onlyheads is faster than letting them be recomputed here. | ||||
If commoninc is given, it must the the result of a prior call to | ||||
findcommonincoming(repo, other, force) to avoid recomputing it here. | ||||
The returned tuple is meant to be passed to changelog.findmissing.''' | ||||
common, _any, _hds = commoninc or findcommonincoming(repo, other, force=force) | ||||
return (common, onlyheads or repo.heads()) | ||||
Dirkjan Ochtman
|
r11301 | 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, | ||||
Pierre-Yves David
|
r15485 | outgoing, common) where outgoing is 0 if there are no outgoing | ||
Dirkjan Ochtman
|
r11301 | changesets and 1 if there are, but we refuse to push them | ||
Pierre-Yves David
|
r15485 | (e.g. would create new remote heads). The third element "common" | ||
is the list of heads of the common set between local and remote. | ||||
Dirkjan Ochtman
|
r11301 | |||
Pierre-Yves David
|
r15485 | 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. | ||||
''' | ||||
Peter Arrenbrecht
|
r14213 | commoninc = findcommonincoming(repo, remote, force=force) | ||
common, revs = findcommonoutgoing(repo, remote, onlyheads=revs, | ||||
commoninc=commoninc, force=force) | ||||
_common, inc, remoteheads = commoninc | ||||
Dirkjan Ochtman
|
r11301 | |||
cl = repo.changelog | ||||
Peter Arrenbrecht
|
r14073 | outg = cl.findmissing(common, revs) | ||
Dirkjan Ochtman
|
r11301 | |||
Peter Arrenbrecht
|
r14073 | if not outg: | ||
Dirkjan Ochtman
|
r11301 | repo.ui.status(_("no changes found\n")) | ||
Pierre-Yves David
|
r15485 | return None, 1, common | ||
Dirkjan Ochtman
|
r11301 | |||
Benoit Boissinot
|
r11577 | if not force and remoteheads != [nullid]: | ||
Dirkjan Ochtman
|
r11301 | 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 | ||||
Martin Geisler
|
r11429 | branchnames = ', '.join(sorted(newbranches)) | ||
Benoit Boissinot
|
r11574 | raise util.Abort(_("push creates new remote branches: %s!") | ||
% branchnames, | ||||
hint=_("use 'hg push --new-branch' to create" | ||||
" new remote branches")) | ||||
Dirkjan Ochtman
|
r11301 | 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: | ||||
Benoit Boissinot
|
r11577 | 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): | ||||
Dirkjan Ochtman
|
r11301 | 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) | ||||
else: | ||||
Benoit Boissinot
|
r11578 | # 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) | ||||
Dirkjan Ochtman
|
r11301 | 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) | ||||
Benoit Boissinot
|
r11578 | 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 | ||||
Adrian Buehlmann
|
r12998 | # error message, depending on unsynced status, is displayed. | ||
error = None | ||||
Benoit Boissinot
|
r11578 | for branch in branches: | ||
Adrian Buehlmann
|
r12998 | newhs = set(newmap[branch]) | ||
oldhs = set(oldmap[branch]) | ||||
if len(newhs) > len(oldhs): | ||||
Adrian Buehlmann
|
r14525 | dhs = list(newhs - oldhs) | ||
Adrian Buehlmann
|
r12998 | if error is None: | ||
Pierre-Yves David
|
r15292 | if branch not in ('default', None): | ||
Adrian Buehlmann
|
r14525 | 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]) | ||||
Adrian Buehlmann
|
r12998 | 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") | ||||
Pierre-Yves David
|
r15292 | if branch is not None: | ||
Mads Kiilerich
|
r15497 | repo.ui.note(_("new remote heads on branch '%s'\n") % branch) | ||
Adrian Buehlmann
|
r14525 | for h in dhs: | ||
Mads Kiilerich
|
r15497 | repo.ui.note(_("new remote head %s\n") % short(h)) | ||
Adrian Buehlmann
|
r12998 | if error: | ||
raise util.Abort(error, hint=hint) | ||||
Benoit Boissinot
|
r11578 | |||
# 6. Check for unsynced changes on involved branches. | ||||
if unsynced: | ||||
repo.ui.warn(_("note: unsynced remote changes!\n")) | ||||
Dirkjan Ochtman
|
r11301 | |||
if revs is None: | ||||
# use the fast path, no race possible on push | ||||
Peter Arrenbrecht
|
r14073 | cg = repo._changegroup(outg, 'push') | ||
Dirkjan Ochtman
|
r11301 | else: | ||
Peter Arrenbrecht
|
r14073 | cg = repo.getbundle('push', heads=revs, common=common) | ||
Pierre-Yves David
|
r15485 | # 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 | ||||