|
1 from __future__ import absolute_import |
|
2 |
|
3 import functools |
|
4 |
|
5 from mercurial import ( |
|
6 discovery, |
|
7 extensions, |
|
8 phases, |
|
9 ) |
|
10 |
|
11 |
|
12 from . import ( |
|
13 compat, |
|
14 exthelper, |
|
15 ) |
|
16 |
|
17 eh = exthelper.exthelper() |
|
18 |
|
19 |
|
20 @eh.uisetup |
|
21 def uisetup(ui): |
|
22 extensions.wrapfunction(discovery, '_postprocessobsolete', _postprocessobsolete) |
|
23 |
|
24 def branchinfo(pushop, repo, node): |
|
25 return repo[node].branch() |
|
26 |
|
27 # taken from 7d5455b988ec + branchinfo abstraction. |
|
28 def _postprocessobsolete(orig, pushop, futurecommon, candidate_newhs): |
|
29 """post process the list of new heads with obsolescence information |
|
30 |
|
31 Exists as a sub-function to contain the complexity and allow extensions to |
|
32 experiment with smarter logic. |
|
33 |
|
34 Returns (newheads, discarded_heads) tuple |
|
35 """ |
|
36 pushingmarkerfor = discovery.pushingmarkerfor |
|
37 # known issue |
|
38 # |
|
39 # * We "silently" skip processing on all changeset unknown locally |
|
40 # |
|
41 # * if <nh> is public on the remote, it won't be affected by obsolete |
|
42 # marker and a new is created |
|
43 |
|
44 # define various utilities and containers |
|
45 repo = pushop.repo |
|
46 unfi = repo.unfiltered() |
|
47 torev = compat.getgetrev(unfi.changelog) |
|
48 public = phases.public |
|
49 getphase = unfi._phasecache.phase |
|
50 ispublic = lambda r: getphase(unfi, r) == public |
|
51 ispushed = lambda n: torev(n) in futurecommon |
|
52 hasoutmarker = functools.partial(pushingmarkerfor, unfi.obsstore, ispushed) |
|
53 successorsmarkers = unfi.obsstore.successors |
|
54 newhs = set() # final set of new heads |
|
55 discarded = set() # new head of fully replaced branch |
|
56 |
|
57 localcandidate = set() # candidate heads known locally |
|
58 unknownheads = set() # candidate heads unknown locally |
|
59 for h in candidate_newhs: |
|
60 if h in unfi: |
|
61 localcandidate.add(h) |
|
62 else: |
|
63 if successorsmarkers.get(h) is not None: |
|
64 msg = ( |
|
65 b'checkheads: remote head unknown locally has' |
|
66 b' local marker: %s\n' |
|
67 ) |
|
68 repo.ui.debug(msg % hex(h)) |
|
69 unknownheads.add(h) |
|
70 |
|
71 # fast path the simple case |
|
72 if len(localcandidate) == 1: |
|
73 return unknownheads | set(candidate_newhs), set() |
|
74 |
|
75 # actually process branch replacement |
|
76 while localcandidate: |
|
77 nh = localcandidate.pop() |
|
78 current_branch = branchinfo(pushop, unfi, nh) |
|
79 # run this check early to skip the evaluation of the whole branch |
|
80 if torev(nh) in futurecommon or ispublic(torev(nh)): |
|
81 newhs.add(nh) |
|
82 continue |
|
83 |
|
84 # Get all revs/nodes on the branch exclusive to this head |
|
85 # (already filtered heads are "ignored")) |
|
86 branchrevs = unfi.revs( |
|
87 b'only(%n, (%ln+%ln))', nh, localcandidate, newhs |
|
88 ) |
|
89 |
|
90 branchnodes = [] |
|
91 for r in branchrevs: |
|
92 ctx = unfi[r] |
|
93 if ctx.branch() == current_branch: |
|
94 branchnodes.append(ctx.node()) |
|
95 |
|
96 # The branch won't be hidden on the remote if |
|
97 # * any part of it is public, |
|
98 # * any part of it is considered part of the result by previous logic, |
|
99 # * if we have no markers to push to obsolete it. |
|
100 if ( |
|
101 any(ispublic(torev(n)) for n in branchnodes) |
|
102 or any(torev(n) in futurecommon for n in branchnodes) |
|
103 or any(not hasoutmarker(n) for n in branchnodes) |
|
104 ): |
|
105 newhs.add(nh) |
|
106 else: |
|
107 # note: there is a corner case if there is a merge in the branch. |
|
108 # we might end up with -more- heads. However, these heads are not |
|
109 # "added" by the push, but more by the "removal" on the remote so I |
|
110 # think is a okay to ignore them, |
|
111 discarded.add(nh) |
|
112 newhs |= unknownheads |
|
113 return newhs, discarded |