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