##// END OF EJS Templates
discovery: ensure that missingheads are always heads of everything we tried...
Pierre-Yves David -
r15955:5a14f48d stable
parent child Browse files
Show More
@@ -1,239 +1,238
1 # discovery.py - protocol changeset discovery functions
1 # discovery.py - protocol changeset discovery functions
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid, short
8 from node import nullid, short
9 from i18n import _
9 from i18n import _
10 import util, setdiscovery, treediscovery, phases
10 import util, setdiscovery, treediscovery, phases
11
11
12 def findcommonincoming(repo, remote, heads=None, force=False):
12 def findcommonincoming(repo, remote, heads=None, force=False):
13 """Return a tuple (common, anyincoming, heads) used to identify the common
13 """Return a tuple (common, anyincoming, heads) used to identify the common
14 subset of nodes between repo and remote.
14 subset of nodes between repo and remote.
15
15
16 "common" is a list of (at least) the heads of the common subset.
16 "common" is a list of (at least) the heads of the common subset.
17 "anyincoming" is testable as a boolean indicating if any nodes are missing
17 "anyincoming" is testable as a boolean indicating if any nodes are missing
18 locally. If remote does not support getbundle, this actually is a list of
18 locally. If remote does not support getbundle, this actually is a list of
19 roots of the nodes that would be incoming, to be supplied to
19 roots of the nodes that would be incoming, to be supplied to
20 changegroupsubset. No code except for pull should be relying on this fact
20 changegroupsubset. No code except for pull should be relying on this fact
21 any longer.
21 any longer.
22 "heads" is either the supplied heads, or else the remote's heads.
22 "heads" is either the supplied heads, or else the remote's heads.
23
23
24 If you pass heads and they are all known locally, the reponse lists justs
24 If you pass heads and they are all known locally, the reponse lists justs
25 these heads in "common" and in "heads".
25 these heads in "common" and in "heads".
26
26
27 Please use findcommonoutgoing to compute the set of outgoing nodes to give
27 Please use findcommonoutgoing to compute the set of outgoing nodes to give
28 extensions a good hook into outgoing.
28 extensions a good hook into outgoing.
29 """
29 """
30
30
31 if not remote.capable('getbundle'):
31 if not remote.capable('getbundle'):
32 return treediscovery.findcommonincoming(repo, remote, heads, force)
32 return treediscovery.findcommonincoming(repo, remote, heads, force)
33
33
34 if heads:
34 if heads:
35 allknown = True
35 allknown = True
36 nm = repo.changelog.nodemap
36 nm = repo.changelog.nodemap
37 for h in heads:
37 for h in heads:
38 if nm.get(h) is None:
38 if nm.get(h) is None:
39 allknown = False
39 allknown = False
40 break
40 break
41 if allknown:
41 if allknown:
42 return (heads, False, heads)
42 return (heads, False, heads)
43
43
44 res = setdiscovery.findcommonheads(repo.ui, repo, remote,
44 res = setdiscovery.findcommonheads(repo.ui, repo, remote,
45 abortwhenunrelated=not force)
45 abortwhenunrelated=not force)
46 common, anyinc, srvheads = res
46 common, anyinc, srvheads = res
47 return (list(common), anyinc, heads or list(srvheads))
47 return (list(common), anyinc, heads or list(srvheads))
48
48
49 class outgoing(object):
49 class outgoing(object):
50 '''Represents the set of nodes present in a local repo but not in a
50 '''Represents the set of nodes present in a local repo but not in a
51 (possibly) remote one.
51 (possibly) remote one.
52
52
53 Members:
53 Members:
54
54
55 missing is a list of all nodes present in local but not in remote.
55 missing is a list of all nodes present in local but not in remote.
56 common is a list of all nodes shared between the two repos.
56 common is a list of all nodes shared between the two repos.
57 excluded is the list of missing changeset that shouldn't be sent remotely.
57 excluded is the list of missing changeset that shouldn't be sent remotely.
58 missingheads is the list of heads of missing.
58 missingheads is the list of heads of missing.
59 commonheads is the list of heads of common.
59 commonheads is the list of heads of common.
60
60
61 The sets are computed on demand from the heads, unless provided upfront
61 The sets are computed on demand from the heads, unless provided upfront
62 by discovery.'''
62 by discovery.'''
63
63
64 def __init__(self, revlog, commonheads, missingheads):
64 def __init__(self, revlog, commonheads, missingheads):
65 self.commonheads = commonheads
65 self.commonheads = commonheads
66 self.missingheads = missingheads
66 self.missingheads = missingheads
67 self._revlog = revlog
67 self._revlog = revlog
68 self._common = None
68 self._common = None
69 self._missing = None
69 self._missing = None
70 self.excluded = []
70 self.excluded = []
71
71
72 def _computecommonmissing(self):
72 def _computecommonmissing(self):
73 sets = self._revlog.findcommonmissing(self.commonheads,
73 sets = self._revlog.findcommonmissing(self.commonheads,
74 self.missingheads)
74 self.missingheads)
75 self._common, self._missing = sets
75 self._common, self._missing = sets
76
76
77 @util.propertycache
77 @util.propertycache
78 def common(self):
78 def common(self):
79 if self._common is None:
79 if self._common is None:
80 self._computecommonmissing()
80 self._computecommonmissing()
81 return self._common
81 return self._common
82
82
83 @util.propertycache
83 @util.propertycache
84 def missing(self):
84 def missing(self):
85 if self._missing is None:
85 if self._missing is None:
86 self._computecommonmissing()
86 self._computecommonmissing()
87 return self._missing
87 return self._missing
88
88
89 def findcommonoutgoing(repo, other, onlyheads=None, force=False, commoninc=None):
89 def findcommonoutgoing(repo, other, onlyheads=None, force=False, commoninc=None):
90 '''Return an outgoing instance to identify the nodes present in repo but
90 '''Return an outgoing instance to identify the nodes present in repo but
91 not in other.
91 not in other.
92
92
93 If onlyheads is given, only nodes ancestral to nodes in onlyheads (inclusive)
93 If onlyheads is given, only nodes ancestral to nodes in onlyheads (inclusive)
94 are included. If you already know the local repo's heads, passing them in
94 are included. If you already know the local repo's heads, passing them in
95 onlyheads is faster than letting them be recomputed here.
95 onlyheads is faster than letting them be recomputed here.
96
96
97 If commoninc is given, it must the the result of a prior call to
97 If commoninc is given, it must the the result of a prior call to
98 findcommonincoming(repo, other, force) to avoid recomputing it here.'''
98 findcommonincoming(repo, other, force) to avoid recomputing it here.'''
99 # declare an empty outgoing object to be filled later
99 # declare an empty outgoing object to be filled later
100 og = outgoing(repo.changelog, None, None)
100 og = outgoing(repo.changelog, None, None)
101
101
102 # get common set if not provided
102 # get common set if not provided
103 if commoninc is None:
103 if commoninc is None:
104 commoninc = findcommonincoming(repo, other, force=force)
104 commoninc = findcommonincoming(repo, other, force=force)
105 og.commonheads, _any, _hds = commoninc
105 og.commonheads, _any, _hds = commoninc
106
106
107 # compute outgoing
107 # compute outgoing
108 if not repo._phaseroots[phases.secret]:
108 if not repo._phaseroots[phases.secret]:
109 og.missingheads = onlyheads or repo.heads()
109 og.missingheads = onlyheads or repo.heads()
110 elif onlyheads is None:
110 elif onlyheads is None:
111 # use visible heads as it should be cached
111 # use visible heads as it should be cached
112 og.missingheads = phases.visibleheads(repo)
112 og.missingheads = phases.visibleheads(repo)
113 og.excluded = [ctx.node() for ctx in repo.set('secret()')]
113 og.excluded = [ctx.node() for ctx in repo.set('secret()')]
114 else:
114 else:
115 # compute common, missing and exclude secret stuff
115 # compute common, missing and exclude secret stuff
116 sets = repo.changelog.findcommonmissing(og.commonheads, onlyheads)
116 sets = repo.changelog.findcommonmissing(og.commonheads, onlyheads)
117 og._common, allmissing = sets
117 og._common, allmissing = sets
118 og._missing = missing = []
118 og._missing = missing = []
119 og.excluded = excluded = []
119 og.excluded = excluded = []
120 for node in allmissing:
120 for node in allmissing:
121 if repo[node].phase() >= phases.secret:
121 if repo[node].phase() >= phases.secret:
122 excluded.append(node)
122 excluded.append(node)
123 else:
123 else:
124 missing.append(node)
124 missing.append(node)
125 if excluded:
125 if excluded:
126 # update missing heads
126 # update missing heads
127 rset = repo.set('heads(%ln)', missing)
127 missingheads = phases.newheads(repo, onlyheads, excluded)
128 missingheads = [ctx.node() for ctx in rset]
129 else:
128 else:
130 missingheads = onlyheads
129 missingheads = onlyheads
131 og.missingheads = missingheads
130 og.missingheads = missingheads
132
131
133 return og
132 return og
134
133
135 def checkheads(repo, remote, outgoing, remoteheads, newbranch=False):
134 def checkheads(repo, remote, outgoing, remoteheads, newbranch=False):
136 """Check that a push won't add any outgoing head
135 """Check that a push won't add any outgoing head
137
136
138 raise Abort error and display ui message as needed.
137 raise Abort error and display ui message as needed.
139 """
138 """
140 if remoteheads == [nullid]:
139 if remoteheads == [nullid]:
141 # remote is empty, nothing to check.
140 # remote is empty, nothing to check.
142 return
141 return
143
142
144 cl = repo.changelog
143 cl = repo.changelog
145 if remote.capable('branchmap'):
144 if remote.capable('branchmap'):
146 # Check for each named branch if we're creating new remote heads.
145 # Check for each named branch if we're creating new remote heads.
147 # To be a remote head after push, node must be either:
146 # To be a remote head after push, node must be either:
148 # - unknown locally
147 # - unknown locally
149 # - a local outgoing head descended from update
148 # - a local outgoing head descended from update
150 # - a remote head that's known locally and not
149 # - a remote head that's known locally and not
151 # ancestral to an outgoing head
150 # ancestral to an outgoing head
152
151
153 # 1. Create set of branches involved in the push.
152 # 1. Create set of branches involved in the push.
154 branches = set(repo[n].branch() for n in outgoing.missing)
153 branches = set(repo[n].branch() for n in outgoing.missing)
155
154
156 # 2. Check for new branches on the remote.
155 # 2. Check for new branches on the remote.
157 remotemap = remote.branchmap()
156 remotemap = remote.branchmap()
158 newbranches = branches - set(remotemap)
157 newbranches = branches - set(remotemap)
159 if newbranches and not newbranch: # new branch requires --new-branch
158 if newbranches and not newbranch: # new branch requires --new-branch
160 branchnames = ', '.join(sorted(newbranches))
159 branchnames = ', '.join(sorted(newbranches))
161 raise util.Abort(_("push creates new remote branches: %s!")
160 raise util.Abort(_("push creates new remote branches: %s!")
162 % branchnames,
161 % branchnames,
163 hint=_("use 'hg push --new-branch' to create"
162 hint=_("use 'hg push --new-branch' to create"
164 " new remote branches"))
163 " new remote branches"))
165 branches.difference_update(newbranches)
164 branches.difference_update(newbranches)
166
165
167 # 3. Construct the initial oldmap and newmap dicts.
166 # 3. Construct the initial oldmap and newmap dicts.
168 # They contain information about the remote heads before and
167 # They contain information about the remote heads before and
169 # after the push, respectively.
168 # after the push, respectively.
170 # Heads not found locally are not included in either dict,
169 # Heads not found locally are not included in either dict,
171 # since they won't be affected by the push.
170 # since they won't be affected by the push.
172 # unsynced contains all branches with incoming changesets.
171 # unsynced contains all branches with incoming changesets.
173 oldmap = {}
172 oldmap = {}
174 newmap = {}
173 newmap = {}
175 unsynced = set()
174 unsynced = set()
176 for branch in branches:
175 for branch in branches:
177 remotebrheads = remotemap[branch]
176 remotebrheads = remotemap[branch]
178 prunedbrheads = [h for h in remotebrheads if h in cl.nodemap]
177 prunedbrheads = [h for h in remotebrheads if h in cl.nodemap]
179 oldmap[branch] = prunedbrheads
178 oldmap[branch] = prunedbrheads
180 newmap[branch] = list(prunedbrheads)
179 newmap[branch] = list(prunedbrheads)
181 if len(remotebrheads) > len(prunedbrheads):
180 if len(remotebrheads) > len(prunedbrheads):
182 unsynced.add(branch)
181 unsynced.add(branch)
183
182
184 # 4. Update newmap with outgoing changes.
183 # 4. Update newmap with outgoing changes.
185 # This will possibly add new heads and remove existing ones.
184 # This will possibly add new heads and remove existing ones.
186 ctxgen = (repo[n] for n in outgoing.missing)
185 ctxgen = (repo[n] for n in outgoing.missing)
187 repo._updatebranchcache(newmap, ctxgen)
186 repo._updatebranchcache(newmap, ctxgen)
188
187
189 else:
188 else:
190 # 1-4b. old servers: Check for new topological heads.
189 # 1-4b. old servers: Check for new topological heads.
191 # Construct {old,new}map with branch = None (topological branch).
190 # Construct {old,new}map with branch = None (topological branch).
192 # (code based on _updatebranchcache)
191 # (code based on _updatebranchcache)
193 oldheads = set(h for h in remoteheads if h in cl.nodemap)
192 oldheads = set(h for h in remoteheads if h in cl.nodemap)
194 newheads = oldheads.union(outg)
193 newheads = oldheads.union(outg)
195 if len(newheads) > 1:
194 if len(newheads) > 1:
196 for latest in reversed(outg):
195 for latest in reversed(outg):
197 if latest not in newheads:
196 if latest not in newheads:
198 continue
197 continue
199 minhrev = min(cl.rev(h) for h in newheads)
198 minhrev = min(cl.rev(h) for h in newheads)
200 reachable = cl.reachable(latest, cl.node(minhrev))
199 reachable = cl.reachable(latest, cl.node(minhrev))
201 reachable.remove(latest)
200 reachable.remove(latest)
202 newheads.difference_update(reachable)
201 newheads.difference_update(reachable)
203 branches = set([None])
202 branches = set([None])
204 newmap = {None: newheads}
203 newmap = {None: newheads}
205 oldmap = {None: oldheads}
204 oldmap = {None: oldheads}
206 unsynced = inc and branches or set()
205 unsynced = inc and branches or set()
207
206
208 # 5. Check for new heads.
207 # 5. Check for new heads.
209 # If there are more heads after the push than before, a suitable
208 # If there are more heads after the push than before, a suitable
210 # error message, depending on unsynced status, is displayed.
209 # error message, depending on unsynced status, is displayed.
211 error = None
210 error = None
212 for branch in branches:
211 for branch in branches:
213 newhs = set(newmap[branch])
212 newhs = set(newmap[branch])
214 oldhs = set(oldmap[branch])
213 oldhs = set(oldmap[branch])
215 if len(newhs) > len(oldhs):
214 if len(newhs) > len(oldhs):
216 dhs = list(newhs - oldhs)
215 dhs = list(newhs - oldhs)
217 if error is None:
216 if error is None:
218 if branch not in ('default', None):
217 if branch not in ('default', None):
219 error = _("push creates new remote head %s "
218 error = _("push creates new remote head %s "
220 "on branch '%s'!") % (short(dhs[0]), branch)
219 "on branch '%s'!") % (short(dhs[0]), branch)
221 else:
220 else:
222 error = _("push creates new remote head %s!"
221 error = _("push creates new remote head %s!"
223 ) % short(dhs[0])
222 ) % short(dhs[0])
224 if branch in unsynced:
223 if branch in unsynced:
225 hint = _("you should pull and merge or "
224 hint = _("you should pull and merge or "
226 "use push -f to force")
225 "use push -f to force")
227 else:
226 else:
228 hint = _("did you forget to merge? "
227 hint = _("did you forget to merge? "
229 "use push -f to force")
228 "use push -f to force")
230 if branch is not None:
229 if branch is not None:
231 repo.ui.note(_("new remote heads on branch '%s'\n") % branch)
230 repo.ui.note(_("new remote heads on branch '%s'\n") % branch)
232 for h in dhs:
231 for h in dhs:
233 repo.ui.note(_("new remote head %s\n") % short(h))
232 repo.ui.note(_("new remote head %s\n") % short(h))
234 if error:
233 if error:
235 raise util.Abort(error, hint=hint)
234 raise util.Abort(error, hint=hint)
236
235
237 # 6. Check for unsynced changes on involved branches.
236 # 6. Check for unsynced changes on involved branches.
238 if unsynced:
237 if unsynced:
239 repo.ui.warn(_("note: unsynced remote changes!\n"))
238 repo.ui.warn(_("note: unsynced remote changes!\n"))
General Comments 0
You need to be logged in to leave comments. Login now