comparison mercurial/discovery.py @ 17211:4f321eecbf8d

checkheads: simplify the structure build by preprocessing All useful data are now gathered in a single dictionnary. `branchmapsummary` is renamed to `headssummary` and its return value is greatly simplified.
author Pierre-Yves David <pierre-yves.david@logilab.fr>
date Tue, 17 Jul 2012 16:30:42 +0200
parents 5cd3e526ac37
children 246131d670c2
comparison
equal deleted inserted replaced
17210:ec80ae982689 17211:4f321eecbf8d
147 commonheads = set(og.commonheads) 147 commonheads = set(og.commonheads)
148 og.missingheads = [h for h in og.missingheads if h not in commonheads] 148 og.missingheads = [h for h in og.missingheads if h not in commonheads]
149 149
150 return og 150 return og
151 151
152 def _branchmapsummary(repo, remote, outgoing): 152 def _headssummary(repo, remote, outgoing):
153 """compute a summary of branch and heads status before and after push 153 """compute a summary of branch and heads status before and after push
154 154
155 - oldmap: {'branch': [heads]} mapping for remote 155 return {'branch': ([remoteheads], [newheads], [unsyncedheads])} mapping
156 - newmap: {'branch': [heads]} mapping for local 156
157 - unsynced: set of branch that have unsynced remote changes 157 - branch: the branch name
158 - branches: set of all common branch pushed 158 - remoteheads: the list of remote heads known locally
159 - newbranches: list of plain new pushed branch 159 None is the branch is new
160 - newheads: the new remote heads (known locally) with outgoing pushed
161 - unsyncedheads: the list of remote heads unknown locally.
160 """ 162 """
161 cl = repo.changelog 163 cl = repo.changelog
162 164 headssum = {}
163 # A. Create set of branches involved in the push. 165 # A. Create set of branches involved in the push.
164 branches = set(repo[n].branch() for n in outgoing.missing) 166 branches = set(repo[n].branch() for n in outgoing.missing)
165 remotemap = remote.branchmap() 167 remotemap = remote.branchmap()
166 newbranches = branches - set(remotemap) 168 newbranches = branches - set(remotemap)
167 branches.difference_update(newbranches) 169 branches.difference_update(newbranches)
168 170
169 # B. Construct the initial oldmap and newmap dicts. 171 # A. register remote heads
170 # They contain information about the remote heads before and 172 remotebranches = set()
171 # after the push, respectively. 173 for branch, heads in remote.branchmap().iteritems():
172 # Heads not found locally are not included in either dict, 174 remotebranches.add(branch)
173 # since they won't be affected by the push. 175 known = []
174 # unsynced contains all branches with incoming changesets. 176 unsynced = []
175 oldmap = {} 177 for h in heads:
176 newmap = {} 178 if h in cl.nodemap:
177 unsynced = set() 179 known.append(h)
178 for branch in branches: 180 else:
179 remotebrheads = remotemap[branch] 181 unsynced.append(h)
180 182 headssum[branch] = (known, list(known), unsynced)
181 prunedbrheads = [h for h in remotebrheads if h in cl.nodemap] 183 # B. add new branch data
182 oldmap[branch] = prunedbrheads 184 missingctx = list(repo[n] for n in outgoing.missing)
183 newmap[branch] = list(prunedbrheads) 185 touchedbranches = set()
184 if len(remotebrheads) > len(prunedbrheads): 186 for ctx in missingctx:
185 unsynced.add(branch) 187 branch = ctx.branch()
186 188 touchedbranches.add(branch)
187 # C. Update newmap with outgoing changes. 189 if branch not in headssum:
190 headssum[branch] = (None, [], [])
191
192 # C drop data about untouched branches:
193 for branch in remotebranches - touchedbranches:
194 del headssum[branch]
195
196 # D. Update newmap with outgoing changes.
188 # This will possibly add new heads and remove existing ones. 197 # This will possibly add new heads and remove existing ones.
189 ctxgen = (repo[n] for n in outgoing.missing) 198 newmap = dict((branch, heads[1]) for branch, heads in headssum.iteritems()
190 repo._updatebranchcache(newmap, ctxgen) 199 if heads[0] is not None)
191 return oldmap, newmap, unsynced, branches, newbranches 200 repo._updatebranchcache(newmap, missingctx)
192 201 for branch, newheads in newmap.iteritems():
193 def _oldbranchmapsummary(repo, remoteheads, outgoing, inc=False): 202 headssum[branch][1][:] = newheads
203 return headssum
204
205 def _oldheadssummary(repo, remoteheads, outgoing, inc=False):
194 """Compute branchmapsummary for repo without branchmap support""" 206 """Compute branchmapsummary for repo without branchmap support"""
195 207
196 cl = repo.changelog 208 cl = repo.changelog
197 # 1-4b. old servers: Check for new topological heads. 209 # 1-4b. old servers: Check for new topological heads.
198 # Construct {old,new}map with branch = None (topological branch). 210 # Construct {old,new}map with branch = None (topological branch).
202 # - an element of oldheads 214 # - an element of oldheads
203 # - another element of outgoing.missing 215 # - another element of outgoing.missing
204 # - nullrev 216 # - nullrev
205 # This explains why the new head are very simple to compute. 217 # This explains why the new head are very simple to compute.
206 r = repo.set('heads(%ln + %ln)', oldheads, outgoing.missing) 218 r = repo.set('heads(%ln + %ln)', oldheads, outgoing.missing)
207 branches = set([None]) 219 newheads = list(c.node() for c in r)
208 newmap = {None: list(c.node() for c in r)} 220 unsynced = inc and set([None]) or set()
209 oldmap = {None: oldheads} 221 return {None: (oldheads, newheads, unsynced)}
210 unsynced = inc and branches or set()
211 return oldmap, newmap, unsynced, branches, set()
212 222
213 def checkheads(repo, remote, outgoing, remoteheads, newbranch=False, inc=False): 223 def checkheads(repo, remote, outgoing, remoteheads, newbranch=False, inc=False):
214 """Check that a push won't add any outgoing head 224 """Check that a push won't add any outgoing head
215 225
216 raise Abort error and display ui message as needed. 226 raise Abort error and display ui message as needed.
224 if remoteheads == [nullid]: 234 if remoteheads == [nullid]:
225 # remote is empty, nothing to check. 235 # remote is empty, nothing to check.
226 return 236 return
227 237
228 if remote.capable('branchmap'): 238 if remote.capable('branchmap'):
229 bms = _branchmapsummary(repo, remote, outgoing) 239 headssum = _headssummary(repo, remote, outgoing)
230 else: 240 else:
231 bms = _oldbranchmapsummary(repo, remoteheads, outgoing, inc) 241 headssum = _oldheadssummary(repo, remoteheads, outgoing, inc)
232 oldmap, newmap, unsynced, branches, newbranches = bms 242 newbranches = [branch for branch, heads in headssum.iteritems()
243 if heads[0] is None]
233 # 1. Check for new branches on the remote. 244 # 1. Check for new branches on the remote.
234 if newbranches and not newbranch: # new branch requires --new-branch 245 if newbranches and not newbranch: # new branch requires --new-branch
235 branchnames = ', '.join(sorted(newbranches)) 246 branchnames = ', '.join(sorted(newbranches))
236 raise util.Abort(_("push creates new remote branches: %s!") 247 raise util.Abort(_("push creates new remote branches: %s!")
237 % branchnames, 248 % branchnames,
242 # If there are more heads after the push than before, a suitable 253 # If there are more heads after the push than before, a suitable
243 # error message, depending on unsynced status, is displayed. 254 # error message, depending on unsynced status, is displayed.
244 error = None 255 error = None
245 localbookmarks = repo._bookmarks 256 localbookmarks = repo._bookmarks
246 257
247 for branch in branches: 258 unsynced = False
248 newhs = set(newmap[branch]) 259 for branch, heads in headssum.iteritems():
249 oldhs = set(oldmap[branch]) 260 if heads[0] is None:
261 # Maybe we should abort if we push more that one head
262 # for new branches ?
263 continue
264 if heads[2]:
265 unsynced = True
266 oldhs = set(heads[0])
267 newhs = set(heads[1])
250 dhs = None 268 dhs = None
251 if len(newhs) > len(oldhs): 269 if len(newhs) > len(oldhs):
252 # strip updates to existing remote heads from the new heads list
253 remotebookmarks = remote.listkeys('bookmarks') 270 remotebookmarks = remote.listkeys('bookmarks')
254 bookmarkedheads = set() 271 bookmarkedheads = set()
255 for bm in localbookmarks: 272 for bm in localbookmarks:
256 rnode = remotebookmarks.get(bm) 273 rnode = remotebookmarks.get(bm)
257 if rnode and rnode in repo: 274 if rnode and rnode in repo:
258 lctx, rctx = repo[bm], repo[rnode] 275 lctx, rctx = repo[bm], repo[rnode]
259 if rctx == lctx.ancestor(rctx): 276 if rctx == lctx.ancestor(rctx):
260 bookmarkedheads.add(lctx.node()) 277 bookmarkedheads.add(lctx.node())
278 # strip updates to existing remote heads from the new heads list
261 dhs = list(newhs - bookmarkedheads - oldhs) 279 dhs = list(newhs - bookmarkedheads - oldhs)
262 if dhs: 280 if dhs:
263 if error is None: 281 if error is None:
264 if branch not in ('default', None): 282 if branch not in ('default', None):
265 error = _("push creates new remote head %s " 283 error = _("push creates new remote head %s "
266 "on branch '%s'!") % (short(dhs[0]), branch) 284 "on branch '%s'!") % (short(dhs[0]), branch)
267 else: 285 else:
268 error = _("push creates new remote head %s!" 286 error = _("push creates new remote head %s!"
269 ) % short(dhs[0]) 287 ) % short(dhs[0])
270 if branch in unsynced: 288 if heads[2]: # unsynced
271 hint = _("you should pull and merge or " 289 hint = _("you should pull and merge or "
272 "use push -f to force") 290 "use push -f to force")
273 else: 291 else:
274 hint = _("did you forget to merge? " 292 hint = _("did you forget to merge? "
275 "use push -f to force") 293 "use push -f to force")