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 | 8 | from __future__ import absolute_import |
|
9 | 9 | |
|
10 | import functools | |
|
11 | ||
|
10 | 12 | from .i18n import _ |
|
11 | 13 | from .node import ( |
|
14 | hex, | |
|
12 | 15 | nullid, |
|
13 | 16 | short, |
|
14 | 17 | ) |
@@ -17,7 +20,6 b' from . import (' | |||
|
17 | 20 | bookmarks, |
|
18 | 21 | branchmap, |
|
19 | 22 | error, |
|
20 | obsolete, | |
|
21 | 23 | phases, |
|
22 | 24 | setdiscovery, |
|
23 | 25 | treediscovery, |
@@ -413,38 +415,105 b' def checkheads(pushop):' | |||
|
413 | 415 | def _postprocessobsolete(pushop, futurecommon, candidate_newhs): |
|
414 | 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 | 419 | experiment with smarter logic. |
|
420 | ||
|
418 | 421 | Returns (newheads, discarded_heads) tuple |
|
419 | 422 | """ |
|
420 | # remove future heads which are actually obsoleted by another | |
|
421 | # pushed element: | |
|
423 | # known issue | |
|
422 | 424 | # |
|
423 | # XXX as above, There are several cases this code does not handle | |
|
424 | # XXX properly | |
|
425 | # | |
|
426 | # (1) if <nh> is public, it won't be affected by obsolete marker | |
|
427 | # and a new is created | |
|
425 | # * We "silently" skip processing on all changeset unknown locally | |
|
428 | 426 | # |
|
429 | # (2) if the new heads have ancestors which are not obsolete and | |
|
430 | # not ancestors of any other heads we will have a new head too. | |
|
431 | # | |
|
432 | # These two cases will be easy to handle for known changeset but | |
|
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 | |
|
427 | # * if <nh> is public on the remote, it won't be affected by obsolete | |
|
428 | # marker and a new is created | |
|
429 | ||
|
430 | # define various utilities and containers | |
|
437 | 431 | repo = pushop.repo |
|
438 | newhs = set() | |
|
439 | discarded = set() | |
|
440 | for nh in candidate_newhs: | |
|
441 | if nh in repo and repo[nh].phase() <= phases.public: | |
|
432 | unfi = repo.unfiltered() | |
|
433 | tonode = unfi.changelog.node | |
|
434 | public = 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 | 481 | newhs.add(nh) |
|
443 | 482 | else: |
|
444 | for suc in obsolete.allsuccessors(repo.obsstore, [nh]): | |
|
445 | if suc != nh and suc in futurecommon: | |
|
483 | # note: there is a corner case if there is a merge in the branch. | |
|
484 | # we might end up with -more- heads. However, these heads are not | |
|
485 | # "added" by the push, but more by the "removal" on the remote so I | |
|
486 | # think is a okay to ignore them, | |
|
446 | 487 |
|
|
447 | break | |
|
448 | else: | |
|
449 | newhs.add(nh) | |
|
488 | newhs |= unknownheads | |
|
450 | 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 | 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 | 278 | pushing to $TESTTMP/remote (glob) |
|
261 | 279 | searching for changes |
|
262 | 280 | adding changesets |
General Comments 0
You need to be logged in to leave comments.
Login now