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