##// END OF EJS Templates
phases: move pure phase computation in a function
Laurent Charignon -
r24519:de3acfab default
parent child Browse files
Show More
@@ -1,455 +1,458
1 """ Mercurial phases support code
1 """ Mercurial phases support code
2
2
3 ---
3 ---
4
4
5 Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
5 Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
6 Logilab SA <contact@logilab.fr>
6 Logilab SA <contact@logilab.fr>
7 Augie Fackler <durin42@gmail.com>
7 Augie Fackler <durin42@gmail.com>
8
8
9 This software may be used and distributed according to the terms
9 This software may be used and distributed according to the terms
10 of the GNU General Public License version 2 or any later version.
10 of the GNU General Public License version 2 or any later version.
11
11
12 ---
12 ---
13
13
14 This module implements most phase logic in mercurial.
14 This module implements most phase logic in mercurial.
15
15
16
16
17 Basic Concept
17 Basic Concept
18 =============
18 =============
19
19
20 A 'changeset phase' is an indicator that tells us how a changeset is
20 A 'changeset phase' is an indicator that tells us how a changeset is
21 manipulated and communicated. The details of each phase is described
21 manipulated and communicated. The details of each phase is described
22 below, here we describe the properties they have in common.
22 below, here we describe the properties they have in common.
23
23
24 Like bookmarks, phases are not stored in history and thus are not
24 Like bookmarks, phases are not stored in history and thus are not
25 permanent and leave no audit trail.
25 permanent and leave no audit trail.
26
26
27 First, no changeset can be in two phases at once. Phases are ordered,
27 First, no changeset can be in two phases at once. Phases are ordered,
28 so they can be considered from lowest to highest. The default, lowest
28 so they can be considered from lowest to highest. The default, lowest
29 phase is 'public' - this is the normal phase of existing changesets. A
29 phase is 'public' - this is the normal phase of existing changesets. A
30 child changeset can not be in a lower phase than its parents.
30 child changeset can not be in a lower phase than its parents.
31
31
32 These phases share a hierarchy of traits:
32 These phases share a hierarchy of traits:
33
33
34 immutable shared
34 immutable shared
35 public: X X
35 public: X X
36 draft: X
36 draft: X
37 secret:
37 secret:
38
38
39 Local commits are draft by default.
39 Local commits are draft by default.
40
40
41 Phase Movement and Exchange
41 Phase Movement and Exchange
42 ===========================
42 ===========================
43
43
44 Phase data is exchanged by pushkey on pull and push. Some servers have
44 Phase data is exchanged by pushkey on pull and push. Some servers have
45 a publish option set, we call such a server a "publishing server".
45 a publish option set, we call such a server a "publishing server".
46 Pushing a draft changeset to a publishing server changes the phase to
46 Pushing a draft changeset to a publishing server changes the phase to
47 public.
47 public.
48
48
49 A small list of fact/rules define the exchange of phase:
49 A small list of fact/rules define the exchange of phase:
50
50
51 * old client never changes server states
51 * old client never changes server states
52 * pull never changes server states
52 * pull never changes server states
53 * publish and old server changesets are seen as public by client
53 * publish and old server changesets are seen as public by client
54 * any secret changeset seen in another repository is lowered to at
54 * any secret changeset seen in another repository is lowered to at
55 least draft
55 least draft
56
56
57 Here is the final table summing up the 49 possible use cases of phase
57 Here is the final table summing up the 49 possible use cases of phase
58 exchange:
58 exchange:
59
59
60 server
60 server
61 old publish non-publish
61 old publish non-publish
62 N X N D P N D P
62 N X N D P N D P
63 old client
63 old client
64 pull
64 pull
65 N - X/X - X/D X/P - X/D X/P
65 N - X/X - X/D X/P - X/D X/P
66 X - X/X - X/D X/P - X/D X/P
66 X - X/X - X/D X/P - X/D X/P
67 push
67 push
68 X X/X X/X X/P X/P X/P X/D X/D X/P
68 X X/X X/X X/P X/P X/P X/D X/D X/P
69 new client
69 new client
70 pull
70 pull
71 N - P/X - P/D P/P - D/D P/P
71 N - P/X - P/D P/P - D/D P/P
72 D - P/X - P/D P/P - D/D P/P
72 D - P/X - P/D P/P - D/D P/P
73 P - P/X - P/D P/P - P/D P/P
73 P - P/X - P/D P/P - P/D P/P
74 push
74 push
75 D P/X P/X P/P P/P P/P D/D D/D P/P
75 D P/X P/X P/P P/P P/P D/D D/D P/P
76 P P/X P/X P/P P/P P/P P/P P/P P/P
76 P P/X P/X P/P P/P P/P P/P P/P P/P
77
77
78 Legend:
78 Legend:
79
79
80 A/B = final state on client / state on server
80 A/B = final state on client / state on server
81
81
82 * N = new/not present,
82 * N = new/not present,
83 * P = public,
83 * P = public,
84 * D = draft,
84 * D = draft,
85 * X = not tracked (i.e., the old client or server has no internal
85 * X = not tracked (i.e., the old client or server has no internal
86 way of recording the phase.)
86 way of recording the phase.)
87
87
88 passive = only pushes
88 passive = only pushes
89
89
90
90
91 A cell here can be read like this:
91 A cell here can be read like this:
92
92
93 "When a new client pushes a draft changeset (D) to a publishing
93 "When a new client pushes a draft changeset (D) to a publishing
94 server where it's not present (N), it's marked public on both
94 server where it's not present (N), it's marked public on both
95 sides (P/P)."
95 sides (P/P)."
96
96
97 Note: old client behave as a publishing server with draft only content
97 Note: old client behave as a publishing server with draft only content
98 - other people see it as public
98 - other people see it as public
99 - content is pushed as draft
99 - content is pushed as draft
100
100
101 """
101 """
102
102
103 import os
103 import os
104 import errno
104 import errno
105 from node import nullid, nullrev, bin, hex, short
105 from node import nullid, nullrev, bin, hex, short
106 from i18n import _
106 from i18n import _
107 import util, error
107 import util, error
108
108
109 allphases = public, draft, secret = range(3)
109 allphases = public, draft, secret = range(3)
110 trackedphases = allphases[1:]
110 trackedphases = allphases[1:]
111 phasenames = ['public', 'draft', 'secret']
111 phasenames = ['public', 'draft', 'secret']
112
112
113 def _readroots(repo, phasedefaults=None):
113 def _readroots(repo, phasedefaults=None):
114 """Read phase roots from disk
114 """Read phase roots from disk
115
115
116 phasedefaults is a list of fn(repo, roots) callable, which are
116 phasedefaults is a list of fn(repo, roots) callable, which are
117 executed if the phase roots file does not exist. When phases are
117 executed if the phase roots file does not exist. When phases are
118 being initialized on an existing repository, this could be used to
118 being initialized on an existing repository, this could be used to
119 set selected changesets phase to something else than public.
119 set selected changesets phase to something else than public.
120
120
121 Return (roots, dirty) where dirty is true if roots differ from
121 Return (roots, dirty) where dirty is true if roots differ from
122 what is being stored.
122 what is being stored.
123 """
123 """
124 repo = repo.unfiltered()
124 repo = repo.unfiltered()
125 dirty = False
125 dirty = False
126 roots = [set() for i in allphases]
126 roots = [set() for i in allphases]
127 try:
127 try:
128 f = None
128 f = None
129 if 'HG_PENDING' in os.environ:
129 if 'HG_PENDING' in os.environ:
130 try:
130 try:
131 f = repo.svfs('phaseroots.pending')
131 f = repo.svfs('phaseroots.pending')
132 except IOError, inst:
132 except IOError, inst:
133 if inst.errno != errno.ENOENT:
133 if inst.errno != errno.ENOENT:
134 raise
134 raise
135 if f is None:
135 if f is None:
136 f = repo.svfs('phaseroots')
136 f = repo.svfs('phaseroots')
137 try:
137 try:
138 for line in f:
138 for line in f:
139 phase, nh = line.split()
139 phase, nh = line.split()
140 roots[int(phase)].add(bin(nh))
140 roots[int(phase)].add(bin(nh))
141 finally:
141 finally:
142 f.close()
142 f.close()
143 except IOError, inst:
143 except IOError, inst:
144 if inst.errno != errno.ENOENT:
144 if inst.errno != errno.ENOENT:
145 raise
145 raise
146 if phasedefaults:
146 if phasedefaults:
147 for f in phasedefaults:
147 for f in phasedefaults:
148 roots = f(repo, roots)
148 roots = f(repo, roots)
149 dirty = True
149 dirty = True
150 return roots, dirty
150 return roots, dirty
151
151
152 class phasecache(object):
152 class phasecache(object):
153 def __init__(self, repo, phasedefaults, _load=True):
153 def __init__(self, repo, phasedefaults, _load=True):
154 if _load:
154 if _load:
155 # Cheap trick to allow shallow-copy without copy module
155 # Cheap trick to allow shallow-copy without copy module
156 self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
156 self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
157 self._phaserevs = None
157 self._phaserevs = None
158 self.filterunknown(repo)
158 self.filterunknown(repo)
159 self.opener = repo.svfs
159 self.opener = repo.svfs
160
160
161 def copy(self):
161 def copy(self):
162 # Shallow copy meant to ensure isolation in
162 # Shallow copy meant to ensure isolation in
163 # advance/retractboundary(), nothing more.
163 # advance/retractboundary(), nothing more.
164 ph = self.__class__(None, None, _load=False)
164 ph = self.__class__(None, None, _load=False)
165 ph.phaseroots = self.phaseroots[:]
165 ph.phaseroots = self.phaseroots[:]
166 ph.dirty = self.dirty
166 ph.dirty = self.dirty
167 ph.opener = self.opener
167 ph.opener = self.opener
168 ph._phaserevs = self._phaserevs
168 ph._phaserevs = self._phaserevs
169 return ph
169 return ph
170
170
171 def replace(self, phcache):
171 def replace(self, phcache):
172 for a in 'phaseroots dirty opener _phaserevs'.split():
172 for a in 'phaseroots dirty opener _phaserevs'.split():
173 setattr(self, a, getattr(phcache, a))
173 setattr(self, a, getattr(phcache, a))
174
174
175 def getphaserevsnative(self, repo):
175 def getphaserevsnative(self, repo):
176 repo = repo.unfiltered()
176 repo = repo.unfiltered()
177 nativeroots = []
177 nativeroots = []
178 for phase in trackedphases:
178 for phase in trackedphases:
179 nativeroots.append(map(repo.changelog.rev, self.phaseroots[phase]))
179 nativeroots.append(map(repo.changelog.rev, self.phaseroots[phase]))
180 return repo.changelog.computephases(nativeroots)
180 return repo.changelog.computephases(nativeroots)
181
181
182 def getphaserevs(self, repo):
182 def computephaserevspure(self, repo):
183 if self._phaserevs is None:
184 try:
185 self._phaserevs = self.getphaserevsnative(repo)
186 except AttributeError:
187 repo = repo.unfiltered()
183 repo = repo.unfiltered()
188 revs = [public] * len(repo.changelog)
184 revs = [public] * len(repo.changelog)
189 self._phaserevs = revs
185 self._phaserevs = revs
190 self._populatephaseroots(repo)
186 self._populatephaseroots(repo)
191 for phase in trackedphases:
187 for phase in trackedphases:
192 roots = map(repo.changelog.rev, self.phaseroots[phase])
188 roots = map(repo.changelog.rev, self.phaseroots[phase])
193 if roots:
189 if roots:
194 for rev in roots:
190 for rev in roots:
195 revs[rev] = phase
191 revs[rev] = phase
196 for rev in repo.changelog.descendants(roots):
192 for rev in repo.changelog.descendants(roots):
197 revs[rev] = phase
193 revs[rev] = phase
194
195 def getphaserevs(self, repo):
196 if self._phaserevs is None:
197 try:
198 self._phaserevs = self.getphaserevsnative(repo)
199 except AttributeError:
200 self.computephaserevspure(repo)
198 return self._phaserevs
201 return self._phaserevs
199
202
200 def invalidate(self):
203 def invalidate(self):
201 self._phaserevs = None
204 self._phaserevs = None
202
205
203 def _populatephaseroots(self, repo):
206 def _populatephaseroots(self, repo):
204 """Fills the _phaserevs cache with phases for the roots.
207 """Fills the _phaserevs cache with phases for the roots.
205 """
208 """
206 cl = repo.changelog
209 cl = repo.changelog
207 phaserevs = self._phaserevs
210 phaserevs = self._phaserevs
208 for phase in trackedphases:
211 for phase in trackedphases:
209 roots = map(cl.rev, self.phaseroots[phase])
212 roots = map(cl.rev, self.phaseroots[phase])
210 for root in roots:
213 for root in roots:
211 phaserevs[root] = phase
214 phaserevs[root] = phase
212
215
213 def phase(self, repo, rev):
216 def phase(self, repo, rev):
214 # We need a repo argument here to be able to build _phaserevs
217 # We need a repo argument here to be able to build _phaserevs
215 # if necessary. The repository instance is not stored in
218 # if necessary. The repository instance is not stored in
216 # phasecache to avoid reference cycles. The changelog instance
219 # phasecache to avoid reference cycles. The changelog instance
217 # is not stored because it is a filecache() property and can
220 # is not stored because it is a filecache() property and can
218 # be replaced without us being notified.
221 # be replaced without us being notified.
219 if rev == nullrev:
222 if rev == nullrev:
220 return public
223 return public
221 if rev < nullrev:
224 if rev < nullrev:
222 raise ValueError(_('cannot lookup negative revision'))
225 raise ValueError(_('cannot lookup negative revision'))
223 if self._phaserevs is None or rev >= len(self._phaserevs):
226 if self._phaserevs is None or rev >= len(self._phaserevs):
224 self.invalidate()
227 self.invalidate()
225 self._phaserevs = self.getphaserevs(repo)
228 self._phaserevs = self.getphaserevs(repo)
226 return self._phaserevs[rev]
229 return self._phaserevs[rev]
227
230
228 def write(self):
231 def write(self):
229 if not self.dirty:
232 if not self.dirty:
230 return
233 return
231 f = self.opener('phaseroots', 'w', atomictemp=True)
234 f = self.opener('phaseroots', 'w', atomictemp=True)
232 try:
235 try:
233 self._write(f)
236 self._write(f)
234 finally:
237 finally:
235 f.close()
238 f.close()
236
239
237 def _write(self, fp):
240 def _write(self, fp):
238 for phase, roots in enumerate(self.phaseroots):
241 for phase, roots in enumerate(self.phaseroots):
239 for h in roots:
242 for h in roots:
240 fp.write('%i %s\n' % (phase, hex(h)))
243 fp.write('%i %s\n' % (phase, hex(h)))
241 self.dirty = False
244 self.dirty = False
242
245
243 def _updateroots(self, phase, newroots, tr):
246 def _updateroots(self, phase, newroots, tr):
244 self.phaseroots[phase] = newroots
247 self.phaseroots[phase] = newroots
245 self.invalidate()
248 self.invalidate()
246 self.dirty = True
249 self.dirty = True
247
250
248 tr.addfilegenerator('phase', ('phaseroots',), self._write)
251 tr.addfilegenerator('phase', ('phaseroots',), self._write)
249 tr.hookargs['phases_moved'] = '1'
252 tr.hookargs['phases_moved'] = '1'
250
253
251 def advanceboundary(self, repo, tr, targetphase, nodes):
254 def advanceboundary(self, repo, tr, targetphase, nodes):
252 # Be careful to preserve shallow-copied values: do not update
255 # Be careful to preserve shallow-copied values: do not update
253 # phaseroots values, replace them.
256 # phaseroots values, replace them.
254
257
255 repo = repo.unfiltered()
258 repo = repo.unfiltered()
256 delroots = [] # set of root deleted by this path
259 delroots = [] # set of root deleted by this path
257 for phase in xrange(targetphase + 1, len(allphases)):
260 for phase in xrange(targetphase + 1, len(allphases)):
258 # filter nodes that are not in a compatible phase already
261 # filter nodes that are not in a compatible phase already
259 nodes = [n for n in nodes
262 nodes = [n for n in nodes
260 if self.phase(repo, repo[n].rev()) >= phase]
263 if self.phase(repo, repo[n].rev()) >= phase]
261 if not nodes:
264 if not nodes:
262 break # no roots to move anymore
265 break # no roots to move anymore
263 olds = self.phaseroots[phase]
266 olds = self.phaseroots[phase]
264 roots = set(ctx.node() for ctx in repo.set(
267 roots = set(ctx.node() for ctx in repo.set(
265 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
268 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
266 if olds != roots:
269 if olds != roots:
267 self._updateroots(phase, roots, tr)
270 self._updateroots(phase, roots, tr)
268 # some roots may need to be declared for lower phases
271 # some roots may need to be declared for lower phases
269 delroots.extend(olds - roots)
272 delroots.extend(olds - roots)
270 # declare deleted root in the target phase
273 # declare deleted root in the target phase
271 if targetphase != 0:
274 if targetphase != 0:
272 self.retractboundary(repo, tr, targetphase, delroots)
275 self.retractboundary(repo, tr, targetphase, delroots)
273 repo.invalidatevolatilesets()
276 repo.invalidatevolatilesets()
274
277
275 def retractboundary(self, repo, tr, targetphase, nodes):
278 def retractboundary(self, repo, tr, targetphase, nodes):
276 # Be careful to preserve shallow-copied values: do not update
279 # Be careful to preserve shallow-copied values: do not update
277 # phaseroots values, replace them.
280 # phaseroots values, replace them.
278
281
279 repo = repo.unfiltered()
282 repo = repo.unfiltered()
280 currentroots = self.phaseroots[targetphase]
283 currentroots = self.phaseroots[targetphase]
281 newroots = [n for n in nodes
284 newroots = [n for n in nodes
282 if self.phase(repo, repo[n].rev()) < targetphase]
285 if self.phase(repo, repo[n].rev()) < targetphase]
283 if newroots:
286 if newroots:
284 if nullid in newroots:
287 if nullid in newroots:
285 raise util.Abort(_('cannot change null revision phase'))
288 raise util.Abort(_('cannot change null revision phase'))
286 currentroots = currentroots.copy()
289 currentroots = currentroots.copy()
287 currentroots.update(newroots)
290 currentroots.update(newroots)
288 ctxs = repo.set('roots(%ln::)', currentroots)
291 ctxs = repo.set('roots(%ln::)', currentroots)
289 currentroots.intersection_update(ctx.node() for ctx in ctxs)
292 currentroots.intersection_update(ctx.node() for ctx in ctxs)
290 self._updateroots(targetphase, currentroots, tr)
293 self._updateroots(targetphase, currentroots, tr)
291 repo.invalidatevolatilesets()
294 repo.invalidatevolatilesets()
292
295
293 def filterunknown(self, repo):
296 def filterunknown(self, repo):
294 """remove unknown nodes from the phase boundary
297 """remove unknown nodes from the phase boundary
295
298
296 Nothing is lost as unknown nodes only hold data for their descendants.
299 Nothing is lost as unknown nodes only hold data for their descendants.
297 """
300 """
298 filtered = False
301 filtered = False
299 nodemap = repo.changelog.nodemap # to filter unknown nodes
302 nodemap = repo.changelog.nodemap # to filter unknown nodes
300 for phase, nodes in enumerate(self.phaseroots):
303 for phase, nodes in enumerate(self.phaseroots):
301 missing = sorted(node for node in nodes if node not in nodemap)
304 missing = sorted(node for node in nodes if node not in nodemap)
302 if missing:
305 if missing:
303 for mnode in missing:
306 for mnode in missing:
304 repo.ui.debug(
307 repo.ui.debug(
305 'removing unknown node %s from %i-phase boundary\n'
308 'removing unknown node %s from %i-phase boundary\n'
306 % (short(mnode), phase))
309 % (short(mnode), phase))
307 nodes.symmetric_difference_update(missing)
310 nodes.symmetric_difference_update(missing)
308 filtered = True
311 filtered = True
309 if filtered:
312 if filtered:
310 self.dirty = True
313 self.dirty = True
311 # filterunknown is called by repo.destroyed, we may have no changes in
314 # filterunknown is called by repo.destroyed, we may have no changes in
312 # root but phaserevs contents is certainly invalid (or at least we
315 # root but phaserevs contents is certainly invalid (or at least we
313 # have not proper way to check that). related to issue 3858.
316 # have not proper way to check that). related to issue 3858.
314 #
317 #
315 # The other caller is __init__ that have no _phaserevs initialized
318 # The other caller is __init__ that have no _phaserevs initialized
316 # anyway. If this change we should consider adding a dedicated
319 # anyway. If this change we should consider adding a dedicated
317 # "destroyed" function to phasecache or a proper cache key mechanism
320 # "destroyed" function to phasecache or a proper cache key mechanism
318 # (see branchmap one)
321 # (see branchmap one)
319 self.invalidate()
322 self.invalidate()
320
323
321 def advanceboundary(repo, tr, targetphase, nodes):
324 def advanceboundary(repo, tr, targetphase, nodes):
322 """Add nodes to a phase changing other nodes phases if necessary.
325 """Add nodes to a phase changing other nodes phases if necessary.
323
326
324 This function move boundary *forward* this means that all nodes
327 This function move boundary *forward* this means that all nodes
325 are set in the target phase or kept in a *lower* phase.
328 are set in the target phase or kept in a *lower* phase.
326
329
327 Simplify boundary to contains phase roots only."""
330 Simplify boundary to contains phase roots only."""
328 phcache = repo._phasecache.copy()
331 phcache = repo._phasecache.copy()
329 phcache.advanceboundary(repo, tr, targetphase, nodes)
332 phcache.advanceboundary(repo, tr, targetphase, nodes)
330 repo._phasecache.replace(phcache)
333 repo._phasecache.replace(phcache)
331
334
332 def retractboundary(repo, tr, targetphase, nodes):
335 def retractboundary(repo, tr, targetphase, nodes):
333 """Set nodes back to a phase changing other nodes phases if
336 """Set nodes back to a phase changing other nodes phases if
334 necessary.
337 necessary.
335
338
336 This function move boundary *backward* this means that all nodes
339 This function move boundary *backward* this means that all nodes
337 are set in the target phase or kept in a *higher* phase.
340 are set in the target phase or kept in a *higher* phase.
338
341
339 Simplify boundary to contains phase roots only."""
342 Simplify boundary to contains phase roots only."""
340 phcache = repo._phasecache.copy()
343 phcache = repo._phasecache.copy()
341 phcache.retractboundary(repo, tr, targetphase, nodes)
344 phcache.retractboundary(repo, tr, targetphase, nodes)
342 repo._phasecache.replace(phcache)
345 repo._phasecache.replace(phcache)
343
346
344 def listphases(repo):
347 def listphases(repo):
345 """List phases root for serialization over pushkey"""
348 """List phases root for serialization over pushkey"""
346 keys = {}
349 keys = {}
347 value = '%i' % draft
350 value = '%i' % draft
348 for root in repo._phasecache.phaseroots[draft]:
351 for root in repo._phasecache.phaseroots[draft]:
349 keys[hex(root)] = value
352 keys[hex(root)] = value
350
353
351 if repo.ui.configbool('phases', 'publish', True):
354 if repo.ui.configbool('phases', 'publish', True):
352 # Add an extra data to let remote know we are a publishing
355 # Add an extra data to let remote know we are a publishing
353 # repo. Publishing repo can't just pretend they are old repo.
356 # repo. Publishing repo can't just pretend they are old repo.
354 # When pushing to a publishing repo, the client still need to
357 # When pushing to a publishing repo, the client still need to
355 # push phase boundary
358 # push phase boundary
356 #
359 #
357 # Push do not only push changeset. It also push phase data.
360 # Push do not only push changeset. It also push phase data.
358 # New phase data may apply to common changeset which won't be
361 # New phase data may apply to common changeset which won't be
359 # push (as they are common). Here is a very simple example:
362 # push (as they are common). Here is a very simple example:
360 #
363 #
361 # 1) repo A push changeset X as draft to repo B
364 # 1) repo A push changeset X as draft to repo B
362 # 2) repo B make changeset X public
365 # 2) repo B make changeset X public
363 # 3) repo B push to repo A. X is not pushed but the data that
366 # 3) repo B push to repo A. X is not pushed but the data that
364 # X as now public should
367 # X as now public should
365 #
368 #
366 # The server can't handle it on it's own as it has no idea of
369 # The server can't handle it on it's own as it has no idea of
367 # client phase data.
370 # client phase data.
368 keys['publishing'] = 'True'
371 keys['publishing'] = 'True'
369 return keys
372 return keys
370
373
371 def pushphase(repo, nhex, oldphasestr, newphasestr):
374 def pushphase(repo, nhex, oldphasestr, newphasestr):
372 """List phases root for serialization over pushkey"""
375 """List phases root for serialization over pushkey"""
373 repo = repo.unfiltered()
376 repo = repo.unfiltered()
374 tr = None
377 tr = None
375 lock = repo.lock()
378 lock = repo.lock()
376 try:
379 try:
377 currentphase = repo[nhex].phase()
380 currentphase = repo[nhex].phase()
378 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
381 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
379 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
382 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
380 if currentphase == oldphase and newphase < oldphase:
383 if currentphase == oldphase and newphase < oldphase:
381 tr = repo.transaction('pushkey-phase')
384 tr = repo.transaction('pushkey-phase')
382 advanceboundary(repo, tr, newphase, [bin(nhex)])
385 advanceboundary(repo, tr, newphase, [bin(nhex)])
383 tr.close()
386 tr.close()
384 return 1
387 return 1
385 elif currentphase == newphase:
388 elif currentphase == newphase:
386 # raced, but got correct result
389 # raced, but got correct result
387 return 1
390 return 1
388 else:
391 else:
389 return 0
392 return 0
390 finally:
393 finally:
391 if tr:
394 if tr:
392 tr.release()
395 tr.release()
393 lock.release()
396 lock.release()
394
397
395 def analyzeremotephases(repo, subset, roots):
398 def analyzeremotephases(repo, subset, roots):
396 """Compute phases heads and root in a subset of node from root dict
399 """Compute phases heads and root in a subset of node from root dict
397
400
398 * subset is heads of the subset
401 * subset is heads of the subset
399 * roots is {<nodeid> => phase} mapping. key and value are string.
402 * roots is {<nodeid> => phase} mapping. key and value are string.
400
403
401 Accept unknown element input
404 Accept unknown element input
402 """
405 """
403 repo = repo.unfiltered()
406 repo = repo.unfiltered()
404 # build list from dictionary
407 # build list from dictionary
405 draftroots = []
408 draftroots = []
406 nodemap = repo.changelog.nodemap # to filter unknown nodes
409 nodemap = repo.changelog.nodemap # to filter unknown nodes
407 for nhex, phase in roots.iteritems():
410 for nhex, phase in roots.iteritems():
408 if nhex == 'publishing': # ignore data related to publish option
411 if nhex == 'publishing': # ignore data related to publish option
409 continue
412 continue
410 node = bin(nhex)
413 node = bin(nhex)
411 phase = int(phase)
414 phase = int(phase)
412 if phase == 0:
415 if phase == 0:
413 if node != nullid:
416 if node != nullid:
414 repo.ui.warn(_('ignoring inconsistent public root'
417 repo.ui.warn(_('ignoring inconsistent public root'
415 ' from remote: %s\n') % nhex)
418 ' from remote: %s\n') % nhex)
416 elif phase == 1:
419 elif phase == 1:
417 if node in nodemap:
420 if node in nodemap:
418 draftroots.append(node)
421 draftroots.append(node)
419 else:
422 else:
420 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
423 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
421 % (phase, nhex))
424 % (phase, nhex))
422 # compute heads
425 # compute heads
423 publicheads = newheads(repo, subset, draftroots)
426 publicheads = newheads(repo, subset, draftroots)
424 return publicheads, draftroots
427 return publicheads, draftroots
425
428
426 def newheads(repo, heads, roots):
429 def newheads(repo, heads, roots):
427 """compute new head of a subset minus another
430 """compute new head of a subset minus another
428
431
429 * `heads`: define the first subset
432 * `heads`: define the first subset
430 * `roots`: define the second we subtract from the first"""
433 * `roots`: define the second we subtract from the first"""
431 repo = repo.unfiltered()
434 repo = repo.unfiltered()
432 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
435 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
433 heads, roots, roots, heads)
436 heads, roots, roots, heads)
434 return [c.node() for c in revset]
437 return [c.node() for c in revset]
435
438
436
439
437 def newcommitphase(ui):
440 def newcommitphase(ui):
438 """helper to get the target phase of new commit
441 """helper to get the target phase of new commit
439
442
440 Handle all possible values for the phases.new-commit options.
443 Handle all possible values for the phases.new-commit options.
441
444
442 """
445 """
443 v = ui.config('phases', 'new-commit', draft)
446 v = ui.config('phases', 'new-commit', draft)
444 try:
447 try:
445 return phasenames.index(v)
448 return phasenames.index(v)
446 except ValueError:
449 except ValueError:
447 try:
450 try:
448 return int(v)
451 return int(v)
449 except ValueError:
452 except ValueError:
450 msg = _("phases.new-commit: not a valid phase name ('%s')")
453 msg = _("phases.new-commit: not a valid phase name ('%s')")
451 raise error.ConfigError(msg % v)
454 raise error.ConfigError(msg % v)
452
455
453 def hassecret(repo):
456 def hassecret(repo):
454 """utility function that check if a repo have any secret changeset."""
457 """utility function that check if a repo have any secret changeset."""
455 return bool(repo._phasecache.phaseroots[2])
458 return bool(repo._phasecache.phaseroots[2])
General Comments 0
You need to be logged in to leave comments. Login now