##// END OF EJS Templates
checkheads: upgrade the obsolescence postprocessing logic (issue4354)...
Pierre-Yves David -
r32009:c6cb21dd default
parent child Browse files
Show More
@@ -0,0 +1,72 b''
1 ====================================
2 Testing head checking code: Case B-1
3 ====================================
4
5 Mercurial checks for the introduction of new heads on push. Evolution comes
6 into play to detect if existing branches on the server are being replaced by
7 some of the new one we push.
8
9 This case is part of a series of tests checking this behavior.
10
11 Category B: simple case involving pruned changesets
12 TestCase 1: single pruned changeset
13
14 .. old-state:
15 ..
16 .. * 1 changeset branch
17 ..
18 .. new-state:
19 ..
20 .. * old branch is pruned
21 .. * 1 new unrelated branch
22 ..
23 .. expected-result:
24 ..
25 .. * push allowed
26 ..
27 .. graph-summary:
28 ..
29 .. B
30 .. |
31 .. A |
32 .. |/
33 ..
34
35 $ . $TESTDIR/testlib/push-checkheads-util.sh
36
37 Test setup
38 ----------
39
40 $ mkdir B1
41 $ cd B1
42 $ setuprepos
43 creating basic server and client repo
44 updating to branch default
45 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
46 $ cd client
47 $ hg up 0
48 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
49 $ mkcommit B0
50 created new head
51 $ hg debugobsolete --record-parents `getid "desc(A0)"`
52 $ hg log -G --hidden
53 @ 74ff5441d343 (draft): B0
54 |
55 | x 8aaa48160adc (draft): A0
56 |/
57 o 1e4be0697311 (public): root
58
59
60 Actual testing
61 --------------
62
63 $ hg push
64 pushing to $TESTTMP/B1/server (glob)
65 searching for changes
66 adding changesets
67 adding manifests
68 adding file changes
69 added 1 changesets with 1 changes to 1 files (+1 heads)
70 1 new obsolescence markers
71
72 $ cd ../..
@@ -7,8 +7,11 b''
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import functools
11
10 from .i18n import _
12 from .i18n import _
11 from .node import (
13 from .node import (
14 hex,
12 nullid,
15 nullid,
13 short,
16 short,
14 )
17 )
@@ -17,7 +20,6 b' from . import ('
17 bookmarks,
20 bookmarks,
18 branchmap,
21 branchmap,
19 error,
22 error,
20 obsolete,
21 phases,
23 phases,
22 setdiscovery,
24 setdiscovery,
23 treediscovery,
25 treediscovery,
@@ -413,38 +415,105 b' def checkheads(pushop):'
413 def _postprocessobsolete(pushop, futurecommon, candidate_newhs):
415 def _postprocessobsolete(pushop, futurecommon, candidate_newhs):
414 """post process the list of new heads with obsolescence information
416 """post process the list of new heads with obsolescence information
415
417
416 Exists as a subfunction to contain the complexity and allow extensions to
418 Exists as a sub-function to contain the complexity and allow extensions to
417 experiment with smarter logic.
419 experiment with smarter logic.
420
418 Returns (newheads, discarded_heads) tuple
421 Returns (newheads, discarded_heads) tuple
419 """
422 """
420 # remove future heads which are actually obsoleted by another
423 # known issue
421 # pushed element:
422 #
424 #
423 # XXX as above, There are several cases this code does not handle
425 # * We "silently" skip processing on all changeset unknown locally
424 # XXX properly
425 #
426 # (1) if <nh> is public, it won't be affected by obsolete marker
427 # and a new is created
428 #
426 #
429 # (2) if the new heads have ancestors which are not obsolete and
427 # * if <nh> is public on the remote, it won't be affected by obsolete
430 # not ancestors of any other heads we will have a new head too.
428 # marker and a new is created
431 #
429
432 # These two cases will be easy to handle for known changeset but
430 # define various utilities and containers
433 # much more tricky for unsynced changes.
434 #
435 # In addition, this code is confused by prune as it only looks for
436 # successors of the heads (none if pruned) leading to issue4354
437 repo = pushop.repo
431 repo = pushop.repo
438 newhs = set()
432 unfi = repo.unfiltered()
439 discarded = set()
433 tonode = unfi.changelog.node
440 for nh in candidate_newhs:
434 public = phases.public
441 if nh in repo and repo[nh].phase() <= phases.public:
435 getphase = unfi._phasecache.phase
436 ispublic = (lambda r: getphase(unfi, r) == public)
437 hasoutmarker = functools.partial(pushingmarkerfor, unfi.obsstore,
438 futurecommon)
439 successorsmarkers = unfi.obsstore.successors
440 newhs = set() # final set of new heads
441 discarded = set() # new head of fully replaced branch
442
443 localcandidate = set() # candidate heads known locally
444 unknownheads = set() # candidate heads unknown locally
445 for h in candidate_newhs:
446 if h in unfi:
447 localcandidate.add(h)
448 else:
449 if successorsmarkers.get(h) is not None:
450 msg = ('checkheads: remote head unknown locally has'
451 ' local marker: %s\n')
452 repo.ui.debug(msg % hex(h))
453 unknownheads.add(h)
454
455 # fast path the simple case
456 if len(localcandidate) == 1:
457 return unknownheads | set(candidate_newhs), set()
458
459 # actually process branch replacement
460 while localcandidate:
461 nh = localcandidate.pop()
462 # run this check early to skip the evaluation of the whole branch
463 if (nh in futurecommon
464 or unfi[nh].phase() <= public):
465 newhs.add(nh)
466 continue
467
468 # Get all revs/nodes on the branch exclusive to this head
469 # (already filtered heads are "ignored"))
470 branchrevs = unfi.revs('only(%n, (%ln+%ln))',
471 nh, localcandidate, newhs)
472 branchnodes = [tonode(r) for r in branchrevs]
473
474 # The branch won't be hidden on the remote if
475 # * any part of it is public,
476 # * any part of it is considered part of the result by previous logic,
477 # * if we have no markers to push to obsolete it.
478 if (any(ispublic(r) for r in branchrevs)
479 or any(n in futurecommon for n in branchnodes)
480 or any(not hasoutmarker(n) for n in branchnodes)):
442 newhs.add(nh)
481 newhs.add(nh)
443 else:
482 else:
444 for suc in obsolete.allsuccessors(repo.obsstore, [nh]):
483 # note: there is a corner case if there is a merge in the branch.
445 if suc != nh and suc in futurecommon:
484 # we might end up with -more- heads. However, these heads are not
446 discarded.add(nh)
485 # "added" by the push, but more by the "removal" on the remote so I
447 break
486 # think is a okay to ignore them,
448 else:
487 discarded.add(nh)
449 newhs.add(nh)
488 newhs |= unknownheads
450 return newhs, discarded
489 return newhs, discarded
490
491 def pushingmarkerfor(obsstore, pushset, node):
492 """true if some markers are to be pushed for node
493
494 We cannot just look in to the pushed obsmarkers from the pushop because
495 discovery might have filtered relevant markers. In addition listing all
496 markers relevant to all changesets in the pushed set would be too expensive
497 (O(len(repo)))
498
499 (note: There are cache opportunity in this function. but it would requires
500 a two dimensional stack.)
501 """
502 successorsmarkers = obsstore.successors
503 stack = [node]
504 seen = set(stack)
505 while stack:
506 current = stack.pop()
507 if current in pushset:
508 return True
509 markers = successorsmarkers.get(current, ())
510 # markers fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
511 for m in markers:
512 nexts = m[1] # successors
513 if not nexts: # this is a prune marker
514 nexts = m[5] # parents
515 for n in nexts:
516 if n not in seen:
517 seen.add(n)
518 stack.append(n)
519 return False
@@ -254,9 +254,27 b' setup'
254 @ b4952fcf48cf (public) add base
254 @ b4952fcf48cf (public) add base
255
255
256
256
257 Push should not complain about new heads.
257 We do not have enought data to take the right decision, we should fail
258
259 $ hg push
260 pushing to $TESTTMP/remote (glob)
261 searching for changes
262 remote has heads on branch 'default' that are not known locally: c70b08862e08
263 abort: push creates new remote head 71e3228bffe1!
264 (pull and merge or see 'hg help push' for details about pushing new heads)
265 [255]
258
266
259 $ hg push --traceback
267 Pulling the missing data makes it work
268
269 $ hg pull
270 pulling from $TESTTMP/remote (glob)
271 searching for changes
272 adding changesets
273 adding manifests
274 adding file changes
275 added 1 changesets with 1 changes to 1 files (+1 heads)
276 (run 'hg heads' to see heads)
277 $ hg push
260 pushing to $TESTTMP/remote (glob)
278 pushing to $TESTTMP/remote (glob)
261 searching for changes
279 searching for changes
262 adding changesets
280 adding changesets
General Comments 0
You need to be logged in to leave comments. Login now