##// END OF EJS Templates
phases: make two functions private for phase computation
Laurent Charignon -
r24599:2a73829e default
parent child Browse files
Show More
@@ -1,462 +1,462 b''
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 computephaserevspure(self, repo):
182 def _computephaserevspure(self, repo):
183 repo = repo.unfiltered()
183 repo = repo.unfiltered()
184 revs = [public] * len(repo.changelog)
184 revs = [public] * len(repo.changelog)
185 self._phaserevs = revs
185 self._phaserevs = revs
186 self._populatephaseroots(repo)
186 self._populatephaseroots(repo)
187 for phase in trackedphases:
187 for phase in trackedphases:
188 roots = map(repo.changelog.rev, self.phaseroots[phase])
188 roots = map(repo.changelog.rev, self.phaseroots[phase])
189 if roots:
189 if roots:
190 for rev in roots:
190 for rev in roots:
191 revs[rev] = phase
191 revs[rev] = phase
192 for rev in repo.changelog.descendants(roots):
192 for rev in repo.changelog.descendants(roots):
193 revs[rev] = phase
193 revs[rev] = phase
194
194
195 def getphaserevs(self, repo):
195 def getphaserevs(self, repo):
196 if self._phaserevs is None:
196 if self._phaserevs is None:
197 try:
197 try:
198 if repo.ui.configbool('experimental',
198 if repo.ui.configbool('experimental',
199 'nativephaseskillswitch'):
199 'nativephaseskillswitch'):
200 self.computephaserevspure(repo)
200 self._computephaserevspure(repo)
201 else:
201 else:
202 self._phaserevs = self.getphaserevsnative(repo)
202 self._phaserevs = self._getphaserevsnative(repo)
203 except AttributeError:
203 except AttributeError:
204 self.computephaserevspure(repo)
204 self._computephaserevspure(repo)
205 return self._phaserevs
205 return self._phaserevs
206
206
207 def invalidate(self):
207 def invalidate(self):
208 self._phaserevs = None
208 self._phaserevs = None
209
209
210 def _populatephaseroots(self, repo):
210 def _populatephaseroots(self, repo):
211 """Fills the _phaserevs cache with phases for the roots.
211 """Fills the _phaserevs cache with phases for the roots.
212 """
212 """
213 cl = repo.changelog
213 cl = repo.changelog
214 phaserevs = self._phaserevs
214 phaserevs = self._phaserevs
215 for phase in trackedphases:
215 for phase in trackedphases:
216 roots = map(cl.rev, self.phaseroots[phase])
216 roots = map(cl.rev, self.phaseroots[phase])
217 for root in roots:
217 for root in roots:
218 phaserevs[root] = phase
218 phaserevs[root] = phase
219
219
220 def phase(self, repo, rev):
220 def phase(self, repo, rev):
221 # We need a repo argument here to be able to build _phaserevs
221 # We need a repo argument here to be able to build _phaserevs
222 # if necessary. The repository instance is not stored in
222 # if necessary. The repository instance is not stored in
223 # phasecache to avoid reference cycles. The changelog instance
223 # phasecache to avoid reference cycles. The changelog instance
224 # is not stored because it is a filecache() property and can
224 # is not stored because it is a filecache() property and can
225 # be replaced without us being notified.
225 # be replaced without us being notified.
226 if rev == nullrev:
226 if rev == nullrev:
227 return public
227 return public
228 if rev < nullrev:
228 if rev < nullrev:
229 raise ValueError(_('cannot lookup negative revision'))
229 raise ValueError(_('cannot lookup negative revision'))
230 if self._phaserevs is None or rev >= len(self._phaserevs):
230 if self._phaserevs is None or rev >= len(self._phaserevs):
231 self.invalidate()
231 self.invalidate()
232 self._phaserevs = self.getphaserevs(repo)
232 self._phaserevs = self.getphaserevs(repo)
233 return self._phaserevs[rev]
233 return self._phaserevs[rev]
234
234
235 def write(self):
235 def write(self):
236 if not self.dirty:
236 if not self.dirty:
237 return
237 return
238 f = self.opener('phaseroots', 'w', atomictemp=True)
238 f = self.opener('phaseroots', 'w', atomictemp=True)
239 try:
239 try:
240 self._write(f)
240 self._write(f)
241 finally:
241 finally:
242 f.close()
242 f.close()
243
243
244 def _write(self, fp):
244 def _write(self, fp):
245 for phase, roots in enumerate(self.phaseroots):
245 for phase, roots in enumerate(self.phaseroots):
246 for h in roots:
246 for h in roots:
247 fp.write('%i %s\n' % (phase, hex(h)))
247 fp.write('%i %s\n' % (phase, hex(h)))
248 self.dirty = False
248 self.dirty = False
249
249
250 def _updateroots(self, phase, newroots, tr):
250 def _updateroots(self, phase, newroots, tr):
251 self.phaseroots[phase] = newroots
251 self.phaseroots[phase] = newroots
252 self.invalidate()
252 self.invalidate()
253 self.dirty = True
253 self.dirty = True
254
254
255 tr.addfilegenerator('phase', ('phaseroots',), self._write)
255 tr.addfilegenerator('phase', ('phaseroots',), self._write)
256 tr.hookargs['phases_moved'] = '1'
256 tr.hookargs['phases_moved'] = '1'
257
257
258 def advanceboundary(self, repo, tr, targetphase, nodes):
258 def advanceboundary(self, repo, tr, targetphase, nodes):
259 # Be careful to preserve shallow-copied values: do not update
259 # Be careful to preserve shallow-copied values: do not update
260 # phaseroots values, replace them.
260 # phaseroots values, replace them.
261
261
262 repo = repo.unfiltered()
262 repo = repo.unfiltered()
263 delroots = [] # set of root deleted by this path
263 delroots = [] # set of root deleted by this path
264 for phase in xrange(targetphase + 1, len(allphases)):
264 for phase in xrange(targetphase + 1, len(allphases)):
265 # filter nodes that are not in a compatible phase already
265 # filter nodes that are not in a compatible phase already
266 nodes = [n for n in nodes
266 nodes = [n for n in nodes
267 if self.phase(repo, repo[n].rev()) >= phase]
267 if self.phase(repo, repo[n].rev()) >= phase]
268 if not nodes:
268 if not nodes:
269 break # no roots to move anymore
269 break # no roots to move anymore
270 olds = self.phaseroots[phase]
270 olds = self.phaseroots[phase]
271 roots = set(ctx.node() for ctx in repo.set(
271 roots = set(ctx.node() for ctx in repo.set(
272 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
272 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
273 if olds != roots:
273 if olds != roots:
274 self._updateroots(phase, roots, tr)
274 self._updateroots(phase, roots, tr)
275 # some roots may need to be declared for lower phases
275 # some roots may need to be declared for lower phases
276 delroots.extend(olds - roots)
276 delroots.extend(olds - roots)
277 # declare deleted root in the target phase
277 # declare deleted root in the target phase
278 if targetphase != 0:
278 if targetphase != 0:
279 self.retractboundary(repo, tr, targetphase, delroots)
279 self.retractboundary(repo, tr, targetphase, delroots)
280 repo.invalidatevolatilesets()
280 repo.invalidatevolatilesets()
281
281
282 def retractboundary(self, repo, tr, targetphase, nodes):
282 def retractboundary(self, repo, tr, targetphase, nodes):
283 # Be careful to preserve shallow-copied values: do not update
283 # Be careful to preserve shallow-copied values: do not update
284 # phaseroots values, replace them.
284 # phaseroots values, replace them.
285
285
286 repo = repo.unfiltered()
286 repo = repo.unfiltered()
287 currentroots = self.phaseroots[targetphase]
287 currentroots = self.phaseroots[targetphase]
288 newroots = [n for n in nodes
288 newroots = [n for n in nodes
289 if self.phase(repo, repo[n].rev()) < targetphase]
289 if self.phase(repo, repo[n].rev()) < targetphase]
290 if newroots:
290 if newroots:
291 if nullid in newroots:
291 if nullid in newroots:
292 raise util.Abort(_('cannot change null revision phase'))
292 raise util.Abort(_('cannot change null revision phase'))
293 currentroots = currentroots.copy()
293 currentroots = currentroots.copy()
294 currentroots.update(newroots)
294 currentroots.update(newroots)
295 ctxs = repo.set('roots(%ln::)', currentroots)
295 ctxs = repo.set('roots(%ln::)', currentroots)
296 currentroots.intersection_update(ctx.node() for ctx in ctxs)
296 currentroots.intersection_update(ctx.node() for ctx in ctxs)
297 self._updateroots(targetphase, currentroots, tr)
297 self._updateroots(targetphase, currentroots, tr)
298 repo.invalidatevolatilesets()
298 repo.invalidatevolatilesets()
299
299
300 def filterunknown(self, repo):
300 def filterunknown(self, repo):
301 """remove unknown nodes from the phase boundary
301 """remove unknown nodes from the phase boundary
302
302
303 Nothing is lost as unknown nodes only hold data for their descendants.
303 Nothing is lost as unknown nodes only hold data for their descendants.
304 """
304 """
305 filtered = False
305 filtered = False
306 nodemap = repo.changelog.nodemap # to filter unknown nodes
306 nodemap = repo.changelog.nodemap # to filter unknown nodes
307 for phase, nodes in enumerate(self.phaseroots):
307 for phase, nodes in enumerate(self.phaseroots):
308 missing = sorted(node for node in nodes if node not in nodemap)
308 missing = sorted(node for node in nodes if node not in nodemap)
309 if missing:
309 if missing:
310 for mnode in missing:
310 for mnode in missing:
311 repo.ui.debug(
311 repo.ui.debug(
312 'removing unknown node %s from %i-phase boundary\n'
312 'removing unknown node %s from %i-phase boundary\n'
313 % (short(mnode), phase))
313 % (short(mnode), phase))
314 nodes.symmetric_difference_update(missing)
314 nodes.symmetric_difference_update(missing)
315 filtered = True
315 filtered = True
316 if filtered:
316 if filtered:
317 self.dirty = True
317 self.dirty = True
318 # filterunknown is called by repo.destroyed, we may have no changes in
318 # filterunknown is called by repo.destroyed, we may have no changes in
319 # root but phaserevs contents is certainly invalid (or at least we
319 # root but phaserevs contents is certainly invalid (or at least we
320 # have not proper way to check that). related to issue 3858.
320 # have not proper way to check that). related to issue 3858.
321 #
321 #
322 # The other caller is __init__ that have no _phaserevs initialized
322 # The other caller is __init__ that have no _phaserevs initialized
323 # anyway. If this change we should consider adding a dedicated
323 # anyway. If this change we should consider adding a dedicated
324 # "destroyed" function to phasecache or a proper cache key mechanism
324 # "destroyed" function to phasecache or a proper cache key mechanism
325 # (see branchmap one)
325 # (see branchmap one)
326 self.invalidate()
326 self.invalidate()
327
327
328 def advanceboundary(repo, tr, targetphase, nodes):
328 def advanceboundary(repo, tr, targetphase, nodes):
329 """Add nodes to a phase changing other nodes phases if necessary.
329 """Add nodes to a phase changing other nodes phases if necessary.
330
330
331 This function move boundary *forward* this means that all nodes
331 This function move boundary *forward* this means that all nodes
332 are set in the target phase or kept in a *lower* phase.
332 are set in the target phase or kept in a *lower* phase.
333
333
334 Simplify boundary to contains phase roots only."""
334 Simplify boundary to contains phase roots only."""
335 phcache = repo._phasecache.copy()
335 phcache = repo._phasecache.copy()
336 phcache.advanceboundary(repo, tr, targetphase, nodes)
336 phcache.advanceboundary(repo, tr, targetphase, nodes)
337 repo._phasecache.replace(phcache)
337 repo._phasecache.replace(phcache)
338
338
339 def retractboundary(repo, tr, targetphase, nodes):
339 def retractboundary(repo, tr, targetphase, nodes):
340 """Set nodes back to a phase changing other nodes phases if
340 """Set nodes back to a phase changing other nodes phases if
341 necessary.
341 necessary.
342
342
343 This function move boundary *backward* this means that all nodes
343 This function move boundary *backward* this means that all nodes
344 are set in the target phase or kept in a *higher* phase.
344 are set in the target phase or kept in a *higher* phase.
345
345
346 Simplify boundary to contains phase roots only."""
346 Simplify boundary to contains phase roots only."""
347 phcache = repo._phasecache.copy()
347 phcache = repo._phasecache.copy()
348 phcache.retractboundary(repo, tr, targetphase, nodes)
348 phcache.retractboundary(repo, tr, targetphase, nodes)
349 repo._phasecache.replace(phcache)
349 repo._phasecache.replace(phcache)
350
350
351 def listphases(repo):
351 def listphases(repo):
352 """List phases root for serialization over pushkey"""
352 """List phases root for serialization over pushkey"""
353 keys = {}
353 keys = {}
354 value = '%i' % draft
354 value = '%i' % draft
355 for root in repo._phasecache.phaseroots[draft]:
355 for root in repo._phasecache.phaseroots[draft]:
356 keys[hex(root)] = value
356 keys[hex(root)] = value
357
357
358 if repo.ui.configbool('phases', 'publish', True):
358 if repo.ui.configbool('phases', 'publish', True):
359 # Add an extra data to let remote know we are a publishing
359 # Add an extra data to let remote know we are a publishing
360 # repo. Publishing repo can't just pretend they are old repo.
360 # repo. Publishing repo can't just pretend they are old repo.
361 # When pushing to a publishing repo, the client still need to
361 # When pushing to a publishing repo, the client still need to
362 # push phase boundary
362 # push phase boundary
363 #
363 #
364 # Push do not only push changeset. It also push phase data.
364 # Push do not only push changeset. It also push phase data.
365 # New phase data may apply to common changeset which won't be
365 # New phase data may apply to common changeset which won't be
366 # push (as they are common). Here is a very simple example:
366 # push (as they are common). Here is a very simple example:
367 #
367 #
368 # 1) repo A push changeset X as draft to repo B
368 # 1) repo A push changeset X as draft to repo B
369 # 2) repo B make changeset X public
369 # 2) repo B make changeset X public
370 # 3) repo B push to repo A. X is not pushed but the data that
370 # 3) repo B push to repo A. X is not pushed but the data that
371 # X as now public should
371 # X as now public should
372 #
372 #
373 # The server can't handle it on it's own as it has no idea of
373 # The server can't handle it on it's own as it has no idea of
374 # client phase data.
374 # client phase data.
375 keys['publishing'] = 'True'
375 keys['publishing'] = 'True'
376 return keys
376 return keys
377
377
378 def pushphase(repo, nhex, oldphasestr, newphasestr):
378 def pushphase(repo, nhex, oldphasestr, newphasestr):
379 """List phases root for serialization over pushkey"""
379 """List phases root for serialization over pushkey"""
380 repo = repo.unfiltered()
380 repo = repo.unfiltered()
381 tr = None
381 tr = None
382 lock = repo.lock()
382 lock = repo.lock()
383 try:
383 try:
384 currentphase = repo[nhex].phase()
384 currentphase = repo[nhex].phase()
385 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
385 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
386 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
386 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
387 if currentphase == oldphase and newphase < oldphase:
387 if currentphase == oldphase and newphase < oldphase:
388 tr = repo.transaction('pushkey-phase')
388 tr = repo.transaction('pushkey-phase')
389 advanceboundary(repo, tr, newphase, [bin(nhex)])
389 advanceboundary(repo, tr, newphase, [bin(nhex)])
390 tr.close()
390 tr.close()
391 return 1
391 return 1
392 elif currentphase == newphase:
392 elif currentphase == newphase:
393 # raced, but got correct result
393 # raced, but got correct result
394 return 1
394 return 1
395 else:
395 else:
396 return 0
396 return 0
397 finally:
397 finally:
398 if tr:
398 if tr:
399 tr.release()
399 tr.release()
400 lock.release()
400 lock.release()
401
401
402 def analyzeremotephases(repo, subset, roots):
402 def analyzeremotephases(repo, subset, roots):
403 """Compute phases heads and root in a subset of node from root dict
403 """Compute phases heads and root in a subset of node from root dict
404
404
405 * subset is heads of the subset
405 * subset is heads of the subset
406 * roots is {<nodeid> => phase} mapping. key and value are string.
406 * roots is {<nodeid> => phase} mapping. key and value are string.
407
407
408 Accept unknown element input
408 Accept unknown element input
409 """
409 """
410 repo = repo.unfiltered()
410 repo = repo.unfiltered()
411 # build list from dictionary
411 # build list from dictionary
412 draftroots = []
412 draftroots = []
413 nodemap = repo.changelog.nodemap # to filter unknown nodes
413 nodemap = repo.changelog.nodemap # to filter unknown nodes
414 for nhex, phase in roots.iteritems():
414 for nhex, phase in roots.iteritems():
415 if nhex == 'publishing': # ignore data related to publish option
415 if nhex == 'publishing': # ignore data related to publish option
416 continue
416 continue
417 node = bin(nhex)
417 node = bin(nhex)
418 phase = int(phase)
418 phase = int(phase)
419 if phase == 0:
419 if phase == 0:
420 if node != nullid:
420 if node != nullid:
421 repo.ui.warn(_('ignoring inconsistent public root'
421 repo.ui.warn(_('ignoring inconsistent public root'
422 ' from remote: %s\n') % nhex)
422 ' from remote: %s\n') % nhex)
423 elif phase == 1:
423 elif phase == 1:
424 if node in nodemap:
424 if node in nodemap:
425 draftroots.append(node)
425 draftroots.append(node)
426 else:
426 else:
427 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
427 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
428 % (phase, nhex))
428 % (phase, nhex))
429 # compute heads
429 # compute heads
430 publicheads = newheads(repo, subset, draftroots)
430 publicheads = newheads(repo, subset, draftroots)
431 return publicheads, draftroots
431 return publicheads, draftroots
432
432
433 def newheads(repo, heads, roots):
433 def newheads(repo, heads, roots):
434 """compute new head of a subset minus another
434 """compute new head of a subset minus another
435
435
436 * `heads`: define the first subset
436 * `heads`: define the first subset
437 * `roots`: define the second we subtract from the first"""
437 * `roots`: define the second we subtract from the first"""
438 repo = repo.unfiltered()
438 repo = repo.unfiltered()
439 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
439 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
440 heads, roots, roots, heads)
440 heads, roots, roots, heads)
441 return [c.node() for c in revset]
441 return [c.node() for c in revset]
442
442
443
443
444 def newcommitphase(ui):
444 def newcommitphase(ui):
445 """helper to get the target phase of new commit
445 """helper to get the target phase of new commit
446
446
447 Handle all possible values for the phases.new-commit options.
447 Handle all possible values for the phases.new-commit options.
448
448
449 """
449 """
450 v = ui.config('phases', 'new-commit', draft)
450 v = ui.config('phases', 'new-commit', draft)
451 try:
451 try:
452 return phasenames.index(v)
452 return phasenames.index(v)
453 except ValueError:
453 except ValueError:
454 try:
454 try:
455 return int(v)
455 return int(v)
456 except ValueError:
456 except ValueError:
457 msg = _("phases.new-commit: not a valid phase name ('%s')")
457 msg = _("phases.new-commit: not a valid phase name ('%s')")
458 raise error.ConfigError(msg % v)
458 raise error.ConfigError(msg % v)
459
459
460 def hassecret(repo):
460 def hassecret(repo):
461 """utility function that check if a repo have any secret changeset."""
461 """utility function that check if a repo have any secret changeset."""
462 return bool(repo._phasecache.phaseroots[2])
462 return bool(repo._phasecache.phaseroots[2])
General Comments 0
You need to be logged in to leave comments. Login now