##// END OF EJS Templates
phases: add list of string to access phase name
Pierre-Yves David -
r15821:e3ee8bf5 default
parent child Browse files
Show More
@@ -1,282 +1,283 b''
1 1 """ Mercurial phases support code
2 2
3 3 ---
4 4
5 5 Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
6 6 Logilab SA <contact@logilab.fr>
7 7 Augie Fackler <durin42@gmail.com>
8 8
9 9 This software may be used and distributed according to the terms of the
10 10 GNU General Public License version 2 or any later version.
11 11
12 12 ---
13 13
14 14 This module implements most phase logic in mercurial.
15 15
16 16
17 17 Basic Concept
18 18 =============
19 19
20 20 A 'changeset phases' is an indicator that tells us how a changeset is
21 21 manipulated and communicated. The details of each phase is described below,
22 22 here we describe the properties they have in common.
23 23
24 24 Like bookmarks, phases are not stored in history and thus are not permanent and
25 25 leave no audit trail.
26 26
27 27 First, no changeset can be in two phases at once. Phases are ordered, so they
28 28 can be considered from lowest to highest. The default, lowest phase is 'public'
29 29 - this is the normal phase of existing changesets. A child changeset can not be
30 30 in a lower phase than its parents.
31 31
32 32 These phases share a hierarchy of traits:
33 33
34 34 immutable shared
35 35 public: X X
36 36 draft: X
37 37 secret:
38 38
39 39 local commits are draft by default
40 40
41 41 Phase movement and exchange
42 42 ============================
43 43
44 44 Phase data are exchanged by pushkey on pull and push. Some server have a
45 45 publish option set, we call them publishing server. Pushing to such server make
46 46 draft changeset publish.
47 47
48 48 A small list of fact/rules define the exchange of phase:
49 49
50 50 * old client never changes server states
51 51 * pull never changes server states
52 52 * publish and old server csets are seen as public by client
53 53
54 54 * Any secret changeset seens in another repository is lowered to at least draft
55 55
56 56
57 57 Here is the final table summing up the 49 possible usecase of phase exchange:
58 58
59 59 server
60 60 old publish non-publish
61 61 N X N D P N D P
62 62 old client
63 63 pull
64 64 N - X/X - X/D X/P - X/D X/P
65 65 X - X/X - X/D X/P - X/D X/P
66 66 push
67 67 X X/X X/X X/P X/P X/P X/D X/D X/P
68 68 new client
69 69 pull
70 70 N - P/X - P/D P/P - D/D P/P
71 71 D - P/X - P/D P/P - D/D P/P
72 72 P - P/X - P/D P/P - P/D P/P
73 73 push
74 74 D P/X P/X P/P P/P P/P D/D D/D P/P
75 75 P P/X P/X P/P P/P P/P P/P P/P P/P
76 76
77 77 Legend:
78 78
79 79 A/B = final state on client / state on server
80 80
81 81 * N = new/not present,
82 82 * P = public,
83 83 * D = draft,
84 84 * X = not tracked (ie: the old client or server has no internal way of
85 85 recording the phase.)
86 86
87 87 passive = only pushes
88 88
89 89
90 90 A cell here can be read like this:
91 91
92 92 "When a new client pushes a draft changeset (D) to a publishing server
93 93 where it's not present (N), it's marked public on both sides (P/P)."
94 94
95 95 Note: old client behave as publish server with Draft only content
96 96 - other people see it as public
97 97 - content is pushed as draft
98 98
99 99 """
100 100
101 101 import errno
102 102 from node import nullid, bin, hex, short
103 103 from i18n import _
104 104
105 105 allphases = public, draft, secret = range(3)
106 106 trackedphases = allphases[1:]
107 phasenames = ['public', 'draft', 'secret']
107 108
108 109 def readroots(repo):
109 110 """Read phase roots from disk"""
110 111 roots = [set() for i in allphases]
111 112 roots[0].add(nullid)
112 113 try:
113 114 f = repo.sopener('phaseroots')
114 115 try:
115 116 for line in f:
116 117 phase, nh = line.strip().split()
117 118 roots[int(phase)].add(bin(nh))
118 119 finally:
119 120 f.close()
120 121 except IOError, inst:
121 122 if inst.errno != errno.ENOENT:
122 123 raise
123 124 return roots
124 125
125 126 def writeroots(repo):
126 127 """Write phase roots from disk"""
127 128 f = repo.sopener('phaseroots', 'w', atomictemp=True)
128 129 try:
129 130 for phase, roots in enumerate(repo._phaseroots):
130 131 for h in roots:
131 132 f.write('%i %s\n' % (phase, hex(h)))
132 133 repo._dirtyphases = False
133 134 finally:
134 135 f.close()
135 136
136 137 def filterunknown(repo, phaseroots=None):
137 138 """remove unknown nodes from the phase boundary
138 139
139 140 no data is lost as unknown node only old data for their descentants
140 141 """
141 142 if phaseroots is None:
142 143 phaseroots = repo._phaseroots
143 144 for phase, nodes in enumerate(phaseroots):
144 145 missing = [node for node in nodes if node not in repo]
145 146 if missing:
146 147 for mnode in missing:
147 148 msg = _('Removing unknown node %(n)s from %(p)i-phase boundary')
148 149 repo.ui.debug(msg, {'n': short(mnode), 'p': phase})
149 150 nodes.symmetric_difference_update(missing)
150 151 repo._dirtyphases = True
151 152
152 153 def advanceboundary(repo, targetphase, nodes):
153 154 """Add nodes to a phase changing other nodes phases if necessary.
154 155
155 156 This function move boundary *forward* this means that all nodes are set
156 157 in the target phase or kept in a *lower* phase.
157 158
158 159 Simplify boundary to contains phase roots only."""
159 160 delroots = [] # set of root deleted by this path
160 161 for phase in xrange(targetphase + 1, len(allphases)):
161 162 # filter nodes that are not in a compatible phase already
162 163 # XXX rev phase cache might have been invalidated by a previous loop
163 164 # XXX we need to be smarter here
164 165 nodes = [n for n in nodes if repo[n].phase() >= phase]
165 166 if not nodes:
166 167 break # no roots to move anymore
167 168 roots = repo._phaseroots[phase]
168 169 olds = roots.copy()
169 170 ctxs = list(repo.set('roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
170 171 roots.clear()
171 172 roots.update(ctx.node() for ctx in ctxs)
172 173 if olds != roots:
173 174 # invalidate cache (we probably could be smarter here
174 175 if '_phaserev' in vars(repo):
175 176 del repo._phaserev
176 177 repo._dirtyphases = True
177 178 # some roots may need to be declared for lower phases
178 179 delroots.extend(olds - roots)
179 180 # declare deleted root in the target phase
180 181 if targetphase != 0:
181 182 retractboundary(repo, targetphase, delroots)
182 183
183 184
184 185 def retractboundary(repo, targetphase, nodes):
185 186 """Set nodes back to a phase changing other nodes phases if necessary.
186 187
187 188 This function move boundary *backward* this means that all nodes are set
188 189 in the target phase or kept in a *higher* phase.
189 190
190 191 Simplify boundary to contains phase roots only."""
191 192 currentroots = repo._phaseroots[targetphase]
192 193 newroots = [n for n in nodes if repo[n].phase() < targetphase]
193 194 if newroots:
194 195 currentroots.update(newroots)
195 196 ctxs = repo.set('roots(%ln::)', currentroots)
196 197 currentroots.intersection_update(ctx.node() for ctx in ctxs)
197 198 if '_phaserev' in vars(repo):
198 199 del repo._phaserev
199 200 repo._dirtyphases = True
200 201
201 202
202 203 def listphases(repo):
203 204 """List phases root for serialisation over pushkey"""
204 205 keys = {}
205 206 for phase in trackedphases:
206 207 for root in repo._phaseroots[phase]:
207 208 keys[hex(root)] = '%i' % phase
208 209 if repo.ui.configbool('phases', 'publish', True):
209 210 # Add an extra data to let remote know we are a publishing repo.
210 211 # Publishing repo can't just pretend they are old repo. When pushing to
211 212 # a publishing repo, the client still need to push phase boundary
212 213 #
213 214 # Push do not only push changeset. It also push phase data. New
214 215 # phase data may apply to common changeset which won't be push (as they
215 216 # are common). Here is a very simple example:
216 217 #
217 218 # 1) repo A push changeset X as draft to repo B
218 219 # 2) repo B make changeset X public
219 220 # 3) repo B push to repo A. X is not pushed but the data that X as now
220 221 # public should
221 222 #
222 223 # The server can't handle it on it's own as it has no idea of client
223 224 # phase data.
224 225 keys['publishing'] = 'True'
225 226 return keys
226 227
227 228 def pushphase(repo, nhex, oldphasestr, newphasestr):
228 229 """List phases root for serialisation over pushkey"""
229 230 lock = repo.lock()
230 231 try:
231 232 currentphase = repo[nhex].phase()
232 233 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
233 234 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
234 235 if currentphase == oldphase and newphase < oldphase:
235 236 advanceboundary(repo, newphase, [bin(nhex)])
236 237 return 1
237 238 else:
238 239 return 0
239 240 finally:
240 241 lock.release()
241 242
242 243 def visibleheads(repo):
243 244 """return the set of visible head of this repo"""
244 245 # XXX we want a cache on this
245 246 sroots = repo._phaseroots[secret]
246 247 if sroots:
247 248 # XXX very slow revset. storing heads or secret "boundary" would help.
248 249 revset = repo.set('heads(not (%ln::))', sroots)
249 250
250 251 vheads = [ctx.node() for ctx in revset]
251 252 if not vheads:
252 253 vheads.append(nullid)
253 254 else:
254 255 vheads = repo.heads()
255 256 return vheads
256 257
257 258 def analyzeremotephases(repo, subset, roots):
258 259 """Compute phases heads and root in a subset of node from root dict
259 260
260 261 * subset is heads of the subset
261 262 * roots is {<nodeid> => phase} mapping. key and value are string.
262 263
263 264 Accept unknown element input
264 265 """
265 266 # build list from dictionary
266 267 phaseroots = [[] for p in allphases]
267 268 for nhex, phase in roots.iteritems():
268 269 if nhex == 'publishing': # ignore data related to publish option
269 270 continue
270 271 node = bin(nhex)
271 272 phase = int(phase)
272 273 if node in repo:
273 274 phaseroots[phase].append(node)
274 275 # compute heads
275 276 phaseheads = [[] for p in allphases]
276 277 for phase in allphases[:-1]:
277 278 toproof = phaseroots[phase + 1]
278 279 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
279 280 subset, toproof, toproof, subset)
280 281 phaseheads[phase].extend(c.node() for c in revset)
281 282 return phaseheads, phaseroots
282 283
General Comments 0
You need to be logged in to leave comments. Login now