##// END OF EJS Templates
phases: add a function to compute heads from root
Pierre-Yves David -
r15649:ca7c4254 default
parent child Browse files
Show More
@@ -1,142 +1,169 b''
1 # Mercurial phases support code
1 # Mercurial phases support code
2 #
2 #
3 # Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
3 # Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 # Logilab SA <contact@logilab.fr>
4 # Logilab SA <contact@logilab.fr>
5 # Augie Fackler <durin42@gmail.com>
5 # Augie Fackler <durin42@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 import errno
10 import errno
11 from node import nullid, bin, hex, short
11 from node import nullid, bin, hex, short
12 from i18n import _
12 from i18n import _
13
13
14 allphases = range(2)
14 allphases = range(2)
15 trackedphases = allphases[1:]
15 trackedphases = allphases[1:]
16
16
17 def readroots(repo):
17 def readroots(repo):
18 """Read phase roots from disk"""
18 """Read phase roots from disk"""
19 roots = [set() for i in allphases]
19 roots = [set() for i in allphases]
20 roots[0].add(nullid)
20 roots[0].add(nullid)
21 try:
21 try:
22 f = repo.sopener('phaseroots')
22 f = repo.sopener('phaseroots')
23 try:
23 try:
24 for line in f:
24 for line in f:
25 phase, nh = line.strip().split()
25 phase, nh = line.strip().split()
26 roots[int(phase)].add(bin(nh))
26 roots[int(phase)].add(bin(nh))
27 finally:
27 finally:
28 f.close()
28 f.close()
29 except IOError, inst:
29 except IOError, inst:
30 if inst.errno != errno.ENOENT:
30 if inst.errno != errno.ENOENT:
31 raise
31 raise
32 return roots
32 return roots
33
33
34 def writeroots(repo):
34 def writeroots(repo):
35 """Write phase roots from disk"""
35 """Write phase roots from disk"""
36 f = repo.sopener('phaseroots', 'w', atomictemp=True)
36 f = repo.sopener('phaseroots', 'w', atomictemp=True)
37 try:
37 try:
38 for phase, roots in enumerate(repo._phaseroots):
38 for phase, roots in enumerate(repo._phaseroots):
39 for h in roots:
39 for h in roots:
40 f.write('%i %s\n' % (phase, hex(h)))
40 f.write('%i %s\n' % (phase, hex(h)))
41 repo._dirtyphases = False
41 repo._dirtyphases = False
42 finally:
42 finally:
43 f.close()
43 f.close()
44
44
45 def filterunknown(repo, phaseroots=None):
45 def filterunknown(repo, phaseroots=None):
46 """remove unknown nodes from the phase boundary
46 """remove unknown nodes from the phase boundary
47
47
48 no data is lost as unknown node only old data for their descentants
48 no data is lost as unknown node only old data for their descentants
49 """
49 """
50 if phaseroots is None:
50 if phaseroots is None:
51 phaseroots = repo._phaseroots
51 phaseroots = repo._phaseroots
52 for phase, nodes in enumerate(phaseroots):
52 for phase, nodes in enumerate(phaseroots):
53 missing = [node for node in nodes if node not in repo]
53 missing = [node for node in nodes if node not in repo]
54 if missing:
54 if missing:
55 for mnode in missing:
55 for mnode in missing:
56 msg = _('Removing unknown node %(n)s from %(p)i-phase boundary')
56 msg = _('Removing unknown node %(n)s from %(p)i-phase boundary')
57 repo.ui.debug(msg, {'n': short(mnode), 'p': phase})
57 repo.ui.debug(msg, {'n': short(mnode), 'p': phase})
58 nodes.symmetric_difference_update(missing)
58 nodes.symmetric_difference_update(missing)
59 repo._dirtyphases = True
59 repo._dirtyphases = True
60
60
61 def advanceboundary(repo, targetphase, nodes):
61 def advanceboundary(repo, targetphase, nodes):
62 """Add nodes to a phase changing other nodes phases if necessary.
62 """Add nodes to a phase changing other nodes phases if necessary.
63
63
64 This function move boundary *forward* this means that all nodes are set
64 This function move boundary *forward* this means that all nodes are set
65 in the target phase or kept in a *lower* phase.
65 in the target phase or kept in a *lower* phase.
66
66
67 Simplify boundary to contains phase roots only."""
67 Simplify boundary to contains phase roots only."""
68 for phase in xrange(targetphase + 1, len(allphases)):
68 for phase in xrange(targetphase + 1, len(allphases)):
69 # filter nodes that are not in a compatible phase already
69 # filter nodes that are not in a compatible phase already
70 # XXX rev phase cache might have been invalidated by a previous loop
70 # XXX rev phase cache might have been invalidated by a previous loop
71 # XXX we need to be smarter here
71 # XXX we need to be smarter here
72 nodes = [n for n in nodes if repo[n].phase() >= phase]
72 nodes = [n for n in nodes if repo[n].phase() >= phase]
73 if not nodes:
73 if not nodes:
74 break # no roots to move anymore
74 break # no roots to move anymore
75 roots = repo._phaseroots[phase]
75 roots = repo._phaseroots[phase]
76 olds = roots.copy()
76 olds = roots.copy()
77 ctxs = list(repo.set('roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
77 ctxs = list(repo.set('roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
78 roots.clear()
78 roots.clear()
79 roots.update(ctx.node() for ctx in ctxs)
79 roots.update(ctx.node() for ctx in ctxs)
80 if olds != roots:
80 if olds != roots:
81 # invalidate cache (we probably could be smarter here
81 # invalidate cache (we probably could be smarter here
82 if '_phaserev' in vars(repo):
82 if '_phaserev' in vars(repo):
83 del repo._phaserev
83 del repo._phaserev
84 repo._dirtyphases = True
84 repo._dirtyphases = True
85
85
86 def retractboundary(repo, targetphase, nodes):
86 def retractboundary(repo, targetphase, nodes):
87 """Set nodes back to a phase changing other nodes phases if necessary.
87 """Set nodes back to a phase changing other nodes phases if necessary.
88
88
89 This function move boundary *backward* this means that all nodes are set
89 This function move boundary *backward* this means that all nodes are set
90 in the target phase or kept in a *higher* phase.
90 in the target phase or kept in a *higher* phase.
91
91
92 Simplify boundary to contains phase roots only."""
92 Simplify boundary to contains phase roots only."""
93 currentroots = repo._phaseroots[targetphase]
93 currentroots = repo._phaseroots[targetphase]
94 newroots = [n for n in nodes if repo[n].phase() < targetphase]
94 newroots = [n for n in nodes if repo[n].phase() < targetphase]
95 if newroots:
95 if newroots:
96 currentroots.update(newroots)
96 currentroots.update(newroots)
97 ctxs = repo.set('roots(%ln::)', currentroots)
97 ctxs = repo.set('roots(%ln::)', currentroots)
98 currentroots.intersection_update(ctx.node() for ctx in ctxs)
98 currentroots.intersection_update(ctx.node() for ctx in ctxs)
99 if '_phaserev' in vars(repo):
99 if '_phaserev' in vars(repo):
100 del repo._phaserev
100 del repo._phaserev
101 repo._dirtyphases = True
101 repo._dirtyphases = True
102
102
103
103
104 def listphases(repo):
104 def listphases(repo):
105 """List phases root for serialisation over pushkey"""
105 """List phases root for serialisation over pushkey"""
106 keys = {}
106 keys = {}
107 for phase in trackedphases:
107 for phase in trackedphases:
108 for root in repo._phaseroots[phase]:
108 for root in repo._phaseroots[phase]:
109 keys[hex(root)] = '%i' % phase
109 keys[hex(root)] = '%i' % phase
110 if repo.ui.configbool('phases', 'publish', True):
110 if repo.ui.configbool('phases', 'publish', True):
111 # Add an extra data to let remote know we are a publishing repo.
111 # Add an extra data to let remote know we are a publishing repo.
112 # Publishing repo can't just pretend they are old repo. When pushing to
112 # Publishing repo can't just pretend they are old repo. When pushing to
113 # a publishing repo, the client still need to push phase boundary
113 # a publishing repo, the client still need to push phase boundary
114 #
114 #
115 # Push do not only push changeset. It also push phase data. New
115 # Push do not only push changeset. It also push phase data. New
116 # phase data may apply to common changeset which won't be push (as they
116 # phase data may apply to common changeset which won't be push (as they
117 # are common). Here is a very simple example:
117 # are common). Here is a very simple example:
118 #
118 #
119 # 1) repo A push changeset X as draft to repo B
119 # 1) repo A push changeset X as draft to repo B
120 # 2) repo B make changeset X public
120 # 2) repo B make changeset X public
121 # 3) repo B push to repo A. X is not pushed but the data that X as now
121 # 3) repo B push to repo A. X is not pushed but the data that X as now
122 # public should
122 # public should
123 #
123 #
124 # The server can't handle it on it's own as it has no idea of client
124 # The server can't handle it on it's own as it has no idea of client
125 # phase data.
125 # phase data.
126 keys['publishing'] = 'True'
126 keys['publishing'] = 'True'
127 return keys
127 return keys
128
128
129 def pushphase(repo, nhex, oldphasestr, newphasestr):
129 def pushphase(repo, nhex, oldphasestr, newphasestr):
130 """List phases root for serialisation over pushkey"""
130 """List phases root for serialisation over pushkey"""
131 lock = repo.lock()
131 lock = repo.lock()
132 try:
132 try:
133 currentphase = repo[nhex].phase()
133 currentphase = repo[nhex].phase()
134 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
134 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
135 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
135 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
136 if currentphase == oldphase and newphase < oldphase:
136 if currentphase == oldphase and newphase < oldphase:
137 advanceboundary(repo, newphase, [bin(nhex)])
137 advanceboundary(repo, newphase, [bin(nhex)])
138 return 1
138 return 1
139 else:
139 else:
140 return 0
140 return 0
141 finally:
141 finally:
142 lock.release()
142 lock.release()
143
144 def analyzeremotephases(repo, subset, roots):
145 """Compute phases heads and root in a subset of node from root dict
146
147 * subset is heads of the subset
148 * roots is {<nodeid> => phase} mapping. key and value are string.
149
150 Accept unknown element input
151 """
152 # build list from dictionary
153 phaseroots = [[] for p in allphases]
154 for nhex, phase in roots.iteritems():
155 if nhex == 'publishing': # ignore data related to publish option
156 continue
157 node = bin(nhex)
158 phase = int(phase)
159 if node in repo:
160 phaseroots[phase].append(node)
161 # compute heads
162 phaseheads = [[] for p in allphases]
163 for phase in allphases[:-1]:
164 toproof = phaseroots[phase + 1]
165 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
166 subset, toproof, toproof, subset)
167 phaseheads[phase].extend(c.node() for c in revset)
168 return phaseheads, phaseroots
169
General Comments 0
You need to be logged in to leave comments. Login now