##// END OF EJS Templates
phases: move root phase assignment to it's own function...
Durham Goode -
r22894:c40be72d default
parent child Browse files
Show More
@@ -1,423 +1,435 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 errno
103 import errno
104 from node import nullid, nullrev, bin, hex, short
104 from node import nullid, nullrev, bin, hex, short
105 from i18n import _
105 from i18n import _
106 import util, error
106 import util, error
107
107
108 allphases = public, draft, secret = range(3)
108 allphases = public, draft, secret = range(3)
109 trackedphases = allphases[1:]
109 trackedphases = allphases[1:]
110 phasenames = ['public', 'draft', 'secret']
110 phasenames = ['public', 'draft', 'secret']
111
111
112 def _readroots(repo, phasedefaults=None):
112 def _readroots(repo, phasedefaults=None):
113 """Read phase roots from disk
113 """Read phase roots from disk
114
114
115 phasedefaults is a list of fn(repo, roots) callable, which are
115 phasedefaults is a list of fn(repo, roots) callable, which are
116 executed if the phase roots file does not exist. When phases are
116 executed if the phase roots file does not exist. When phases are
117 being initialized on an existing repository, this could be used to
117 being initialized on an existing repository, this could be used to
118 set selected changesets phase to something else than public.
118 set selected changesets phase to something else than public.
119
119
120 Return (roots, dirty) where dirty is true if roots differ from
120 Return (roots, dirty) where dirty is true if roots differ from
121 what is being stored.
121 what is being stored.
122 """
122 """
123 repo = repo.unfiltered()
123 repo = repo.unfiltered()
124 dirty = False
124 dirty = False
125 roots = [set() for i in allphases]
125 roots = [set() for i in allphases]
126 try:
126 try:
127 f = repo.sopener('phaseroots')
127 f = repo.sopener('phaseroots')
128 try:
128 try:
129 for line in f:
129 for line in f:
130 phase, nh = line.split()
130 phase, nh = line.split()
131 roots[int(phase)].add(bin(nh))
131 roots[int(phase)].add(bin(nh))
132 finally:
132 finally:
133 f.close()
133 f.close()
134 except IOError, inst:
134 except IOError, inst:
135 if inst.errno != errno.ENOENT:
135 if inst.errno != errno.ENOENT:
136 raise
136 raise
137 if phasedefaults:
137 if phasedefaults:
138 for f in phasedefaults:
138 for f in phasedefaults:
139 roots = f(repo, roots)
139 roots = f(repo, roots)
140 dirty = True
140 dirty = True
141 return roots, dirty
141 return roots, dirty
142
142
143 class phasecache(object):
143 class phasecache(object):
144 def __init__(self, repo, phasedefaults, _load=True):
144 def __init__(self, repo, phasedefaults, _load=True):
145 if _load:
145 if _load:
146 # Cheap trick to allow shallow-copy without copy module
146 # Cheap trick to allow shallow-copy without copy module
147 self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
147 self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
148 self._phaserevs = None
148 self._phaserevs = None
149 self.filterunknown(repo)
149 self.filterunknown(repo)
150 self.opener = repo.sopener
150 self.opener = repo.sopener
151
151
152 def copy(self):
152 def copy(self):
153 # Shallow copy meant to ensure isolation in
153 # Shallow copy meant to ensure isolation in
154 # advance/retractboundary(), nothing more.
154 # advance/retractboundary(), nothing more.
155 ph = phasecache(None, None, _load=False)
155 ph = phasecache(None, None, _load=False)
156 ph.phaseroots = self.phaseroots[:]
156 ph.phaseroots = self.phaseroots[:]
157 ph.dirty = self.dirty
157 ph.dirty = self.dirty
158 ph.opener = self.opener
158 ph.opener = self.opener
159 ph._phaserevs = self._phaserevs
159 ph._phaserevs = self._phaserevs
160 return ph
160 return ph
161
161
162 def replace(self, phcache):
162 def replace(self, phcache):
163 for a in 'phaseroots dirty opener _phaserevs'.split():
163 for a in 'phaseroots dirty opener _phaserevs'.split():
164 setattr(self, a, getattr(phcache, a))
164 setattr(self, a, getattr(phcache, a))
165
165
166 def getphaserevs(self, repo):
166 def getphaserevs(self, repo):
167 if self._phaserevs is None:
167 if self._phaserevs is None:
168 repo = repo.unfiltered()
168 repo = repo.unfiltered()
169 revs = [public] * len(repo.changelog)
169 revs = [public] * len(repo.changelog)
170 self._phaserevs = revs
171 self._populatephaseroots(repo)
170 for phase in trackedphases:
172 for phase in trackedphases:
171 roots = map(repo.changelog.rev, self.phaseroots[phase])
173 roots = map(repo.changelog.rev, self.phaseroots[phase])
172 if roots:
174 if roots:
173 for rev in roots:
175 for rev in roots:
174 revs[rev] = phase
176 revs[rev] = phase
175 for rev in repo.changelog.descendants(roots):
177 for rev in repo.changelog.descendants(roots):
176 revs[rev] = phase
178 revs[rev] = phase
177 self._phaserevs = revs
178 return self._phaserevs
179 return self._phaserevs
180
179 def invalidate(self):
181 def invalidate(self):
180 self._phaserevs = None
182 self._phaserevs = None
181
183
184 def _populatephaseroots(self, repo):
185 """Fills the _phaserevs cache with phases for the roots.
186 """
187 cl = repo.changelog
188 phaserevs = self._phaserevs
189 for phase in trackedphases:
190 roots = map(cl.rev, self.phaseroots[phase])
191 for root in roots:
192 phaserevs[root] = phase
193
182 def phase(self, repo, rev):
194 def phase(self, repo, rev):
183 # We need a repo argument here to be able to build _phaserevs
195 # We need a repo argument here to be able to build _phaserevs
184 # if necessary. The repository instance is not stored in
196 # if necessary. The repository instance is not stored in
185 # phasecache to avoid reference cycles. The changelog instance
197 # phasecache to avoid reference cycles. The changelog instance
186 # is not stored because it is a filecache() property and can
198 # is not stored because it is a filecache() property and can
187 # be replaced without us being notified.
199 # be replaced without us being notified.
188 if rev == nullrev:
200 if rev == nullrev:
189 return public
201 return public
190 if rev < nullrev:
202 if rev < nullrev:
191 raise ValueError(_('cannot lookup negative revision'))
203 raise ValueError(_('cannot lookup negative revision'))
192 if self._phaserevs is None or rev >= len(self._phaserevs):
204 if self._phaserevs is None or rev >= len(self._phaserevs):
193 self.invalidate()
205 self.invalidate()
194 self._phaserevs = self.getphaserevs(repo)
206 self._phaserevs = self.getphaserevs(repo)
195 return self._phaserevs[rev]
207 return self._phaserevs[rev]
196
208
197 def write(self):
209 def write(self):
198 if not self.dirty:
210 if not self.dirty:
199 return
211 return
200 f = self.opener('phaseroots', 'w', atomictemp=True)
212 f = self.opener('phaseroots', 'w', atomictemp=True)
201 try:
213 try:
202 self._write(f)
214 self._write(f)
203 finally:
215 finally:
204 f.close()
216 f.close()
205
217
206 def _write(self, fp):
218 def _write(self, fp):
207 for phase, roots in enumerate(self.phaseroots):
219 for phase, roots in enumerate(self.phaseroots):
208 for h in roots:
220 for h in roots:
209 fp.write('%i %s\n' % (phase, hex(h)))
221 fp.write('%i %s\n' % (phase, hex(h)))
210 self.dirty = False
222 self.dirty = False
211
223
212 def _updateroots(self, phase, newroots, tr):
224 def _updateroots(self, phase, newroots, tr):
213 self.phaseroots[phase] = newroots
225 self.phaseroots[phase] = newroots
214 self.invalidate()
226 self.invalidate()
215 self.dirty = True
227 self.dirty = True
216
228
217 tr.addfilegenerator('phase', ('phaseroots',), self._write)
229 tr.addfilegenerator('phase', ('phaseroots',), self._write)
218
230
219 def advanceboundary(self, repo, tr, targetphase, nodes):
231 def advanceboundary(self, repo, tr, targetphase, nodes):
220 # Be careful to preserve shallow-copied values: do not update
232 # Be careful to preserve shallow-copied values: do not update
221 # phaseroots values, replace them.
233 # phaseroots values, replace them.
222
234
223 repo = repo.unfiltered()
235 repo = repo.unfiltered()
224 delroots = [] # set of root deleted by this path
236 delroots = [] # set of root deleted by this path
225 for phase in xrange(targetphase + 1, len(allphases)):
237 for phase in xrange(targetphase + 1, len(allphases)):
226 # filter nodes that are not in a compatible phase already
238 # filter nodes that are not in a compatible phase already
227 nodes = [n for n in nodes
239 nodes = [n for n in nodes
228 if self.phase(repo, repo[n].rev()) >= phase]
240 if self.phase(repo, repo[n].rev()) >= phase]
229 if not nodes:
241 if not nodes:
230 break # no roots to move anymore
242 break # no roots to move anymore
231 olds = self.phaseroots[phase]
243 olds = self.phaseroots[phase]
232 roots = set(ctx.node() for ctx in repo.set(
244 roots = set(ctx.node() for ctx in repo.set(
233 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
245 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
234 if olds != roots:
246 if olds != roots:
235 self._updateroots(phase, roots, tr)
247 self._updateroots(phase, roots, tr)
236 # some roots may need to be declared for lower phases
248 # some roots may need to be declared for lower phases
237 delroots.extend(olds - roots)
249 delroots.extend(olds - roots)
238 # declare deleted root in the target phase
250 # declare deleted root in the target phase
239 if targetphase != 0:
251 if targetphase != 0:
240 self.retractboundary(repo, tr, targetphase, delroots)
252 self.retractboundary(repo, tr, targetphase, delroots)
241 repo.invalidatevolatilesets()
253 repo.invalidatevolatilesets()
242
254
243 def retractboundary(self, repo, tr, targetphase, nodes):
255 def retractboundary(self, repo, tr, targetphase, nodes):
244 # Be careful to preserve shallow-copied values: do not update
256 # Be careful to preserve shallow-copied values: do not update
245 # phaseroots values, replace them.
257 # phaseroots values, replace them.
246
258
247 repo = repo.unfiltered()
259 repo = repo.unfiltered()
248 currentroots = self.phaseroots[targetphase]
260 currentroots = self.phaseroots[targetphase]
249 newroots = [n for n in nodes
261 newroots = [n for n in nodes
250 if self.phase(repo, repo[n].rev()) < targetphase]
262 if self.phase(repo, repo[n].rev()) < targetphase]
251 if newroots:
263 if newroots:
252 if nullid in newroots:
264 if nullid in newroots:
253 raise util.Abort(_('cannot change null revision phase'))
265 raise util.Abort(_('cannot change null revision phase'))
254 currentroots = currentroots.copy()
266 currentroots = currentroots.copy()
255 currentroots.update(newroots)
267 currentroots.update(newroots)
256 ctxs = repo.set('roots(%ln::)', currentroots)
268 ctxs = repo.set('roots(%ln::)', currentroots)
257 currentroots.intersection_update(ctx.node() for ctx in ctxs)
269 currentroots.intersection_update(ctx.node() for ctx in ctxs)
258 self._updateroots(targetphase, currentroots, tr)
270 self._updateroots(targetphase, currentroots, tr)
259 repo.invalidatevolatilesets()
271 repo.invalidatevolatilesets()
260
272
261 def filterunknown(self, repo):
273 def filterunknown(self, repo):
262 """remove unknown nodes from the phase boundary
274 """remove unknown nodes from the phase boundary
263
275
264 Nothing is lost as unknown nodes only hold data for their descendants.
276 Nothing is lost as unknown nodes only hold data for their descendants.
265 """
277 """
266 filtered = False
278 filtered = False
267 nodemap = repo.changelog.nodemap # to filter unknown nodes
279 nodemap = repo.changelog.nodemap # to filter unknown nodes
268 for phase, nodes in enumerate(self.phaseroots):
280 for phase, nodes in enumerate(self.phaseroots):
269 missing = sorted(node for node in nodes if node not in nodemap)
281 missing = sorted(node for node in nodes if node not in nodemap)
270 if missing:
282 if missing:
271 for mnode in missing:
283 for mnode in missing:
272 repo.ui.debug(
284 repo.ui.debug(
273 'removing unknown node %s from %i-phase boundary\n'
285 'removing unknown node %s from %i-phase boundary\n'
274 % (short(mnode), phase))
286 % (short(mnode), phase))
275 nodes.symmetric_difference_update(missing)
287 nodes.symmetric_difference_update(missing)
276 filtered = True
288 filtered = True
277 if filtered:
289 if filtered:
278 self.dirty = True
290 self.dirty = True
279 # filterunknown is called by repo.destroyed, we may have no changes in
291 # filterunknown is called by repo.destroyed, we may have no changes in
280 # root but phaserevs contents is certainly invalid (or at least we
292 # root but phaserevs contents is certainly invalid (or at least we
281 # have not proper way to check that). related to issue 3858.
293 # have not proper way to check that). related to issue 3858.
282 #
294 #
283 # The other caller is __init__ that have no _phaserevs initialized
295 # The other caller is __init__ that have no _phaserevs initialized
284 # anyway. If this change we should consider adding a dedicated
296 # anyway. If this change we should consider adding a dedicated
285 # "destroyed" function to phasecache or a proper cache key mechanism
297 # "destroyed" function to phasecache or a proper cache key mechanism
286 # (see branchmap one)
298 # (see branchmap one)
287 self.invalidate()
299 self.invalidate()
288
300
289 def advanceboundary(repo, tr, targetphase, nodes):
301 def advanceboundary(repo, tr, targetphase, nodes):
290 """Add nodes to a phase changing other nodes phases if necessary.
302 """Add nodes to a phase changing other nodes phases if necessary.
291
303
292 This function move boundary *forward* this means that all nodes
304 This function move boundary *forward* this means that all nodes
293 are set in the target phase or kept in a *lower* phase.
305 are set in the target phase or kept in a *lower* phase.
294
306
295 Simplify boundary to contains phase roots only."""
307 Simplify boundary to contains phase roots only."""
296 phcache = repo._phasecache.copy()
308 phcache = repo._phasecache.copy()
297 phcache.advanceboundary(repo, tr, targetphase, nodes)
309 phcache.advanceboundary(repo, tr, targetphase, nodes)
298 repo._phasecache.replace(phcache)
310 repo._phasecache.replace(phcache)
299
311
300 def retractboundary(repo, tr, targetphase, nodes):
312 def retractboundary(repo, tr, targetphase, nodes):
301 """Set nodes back to a phase changing other nodes phases if
313 """Set nodes back to a phase changing other nodes phases if
302 necessary.
314 necessary.
303
315
304 This function move boundary *backward* this means that all nodes
316 This function move boundary *backward* this means that all nodes
305 are set in the target phase or kept in a *higher* phase.
317 are set in the target phase or kept in a *higher* phase.
306
318
307 Simplify boundary to contains phase roots only."""
319 Simplify boundary to contains phase roots only."""
308 phcache = repo._phasecache.copy()
320 phcache = repo._phasecache.copy()
309 phcache.retractboundary(repo, tr, targetphase, nodes)
321 phcache.retractboundary(repo, tr, targetphase, nodes)
310 repo._phasecache.replace(phcache)
322 repo._phasecache.replace(phcache)
311
323
312 def listphases(repo):
324 def listphases(repo):
313 """List phases root for serialization over pushkey"""
325 """List phases root for serialization over pushkey"""
314 keys = {}
326 keys = {}
315 value = '%i' % draft
327 value = '%i' % draft
316 for root in repo._phasecache.phaseroots[draft]:
328 for root in repo._phasecache.phaseroots[draft]:
317 keys[hex(root)] = value
329 keys[hex(root)] = value
318
330
319 if repo.ui.configbool('phases', 'publish', True):
331 if repo.ui.configbool('phases', 'publish', True):
320 # Add an extra data to let remote know we are a publishing
332 # Add an extra data to let remote know we are a publishing
321 # repo. Publishing repo can't just pretend they are old repo.
333 # repo. Publishing repo can't just pretend they are old repo.
322 # When pushing to a publishing repo, the client still need to
334 # When pushing to a publishing repo, the client still need to
323 # push phase boundary
335 # push phase boundary
324 #
336 #
325 # Push do not only push changeset. It also push phase data.
337 # Push do not only push changeset. It also push phase data.
326 # New phase data may apply to common changeset which won't be
338 # New phase data may apply to common changeset which won't be
327 # push (as they are common). Here is a very simple example:
339 # push (as they are common). Here is a very simple example:
328 #
340 #
329 # 1) repo A push changeset X as draft to repo B
341 # 1) repo A push changeset X as draft to repo B
330 # 2) repo B make changeset X public
342 # 2) repo B make changeset X public
331 # 3) repo B push to repo A. X is not pushed but the data that
343 # 3) repo B push to repo A. X is not pushed but the data that
332 # X as now public should
344 # X as now public should
333 #
345 #
334 # The server can't handle it on it's own as it has no idea of
346 # The server can't handle it on it's own as it has no idea of
335 # client phase data.
347 # client phase data.
336 keys['publishing'] = 'True'
348 keys['publishing'] = 'True'
337 return keys
349 return keys
338
350
339 def pushphase(repo, nhex, oldphasestr, newphasestr):
351 def pushphase(repo, nhex, oldphasestr, newphasestr):
340 """List phases root for serialization over pushkey"""
352 """List phases root for serialization over pushkey"""
341 repo = repo.unfiltered()
353 repo = repo.unfiltered()
342 tr = None
354 tr = None
343 lock = repo.lock()
355 lock = repo.lock()
344 try:
356 try:
345 currentphase = repo[nhex].phase()
357 currentphase = repo[nhex].phase()
346 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
358 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
347 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
359 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
348 if currentphase == oldphase and newphase < oldphase:
360 if currentphase == oldphase and newphase < oldphase:
349 tr = repo.transaction('pushkey-phase')
361 tr = repo.transaction('pushkey-phase')
350 advanceboundary(repo, tr, newphase, [bin(nhex)])
362 advanceboundary(repo, tr, newphase, [bin(nhex)])
351 tr.close()
363 tr.close()
352 return 1
364 return 1
353 elif currentphase == newphase:
365 elif currentphase == newphase:
354 # raced, but got correct result
366 # raced, but got correct result
355 return 1
367 return 1
356 else:
368 else:
357 return 0
369 return 0
358 finally:
370 finally:
359 if tr:
371 if tr:
360 tr.release()
372 tr.release()
361 lock.release()
373 lock.release()
362
374
363 def analyzeremotephases(repo, subset, roots):
375 def analyzeremotephases(repo, subset, roots):
364 """Compute phases heads and root in a subset of node from root dict
376 """Compute phases heads and root in a subset of node from root dict
365
377
366 * subset is heads of the subset
378 * subset is heads of the subset
367 * roots is {<nodeid> => phase} mapping. key and value are string.
379 * roots is {<nodeid> => phase} mapping. key and value are string.
368
380
369 Accept unknown element input
381 Accept unknown element input
370 """
382 """
371 repo = repo.unfiltered()
383 repo = repo.unfiltered()
372 # build list from dictionary
384 # build list from dictionary
373 draftroots = []
385 draftroots = []
374 nodemap = repo.changelog.nodemap # to filter unknown nodes
386 nodemap = repo.changelog.nodemap # to filter unknown nodes
375 for nhex, phase in roots.iteritems():
387 for nhex, phase in roots.iteritems():
376 if nhex == 'publishing': # ignore data related to publish option
388 if nhex == 'publishing': # ignore data related to publish option
377 continue
389 continue
378 node = bin(nhex)
390 node = bin(nhex)
379 phase = int(phase)
391 phase = int(phase)
380 if phase == 0:
392 if phase == 0:
381 if node != nullid:
393 if node != nullid:
382 repo.ui.warn(_('ignoring inconsistent public root'
394 repo.ui.warn(_('ignoring inconsistent public root'
383 ' from remote: %s\n') % nhex)
395 ' from remote: %s\n') % nhex)
384 elif phase == 1:
396 elif phase == 1:
385 if node in nodemap:
397 if node in nodemap:
386 draftroots.append(node)
398 draftroots.append(node)
387 else:
399 else:
388 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
400 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
389 % (phase, nhex))
401 % (phase, nhex))
390 # compute heads
402 # compute heads
391 publicheads = newheads(repo, subset, draftroots)
403 publicheads = newheads(repo, subset, draftroots)
392 return publicheads, draftroots
404 return publicheads, draftroots
393
405
394 def newheads(repo, heads, roots):
406 def newheads(repo, heads, roots):
395 """compute new head of a subset minus another
407 """compute new head of a subset minus another
396
408
397 * `heads`: define the first subset
409 * `heads`: define the first subset
398 * `roots`: define the second we subtract from the first"""
410 * `roots`: define the second we subtract from the first"""
399 repo = repo.unfiltered()
411 repo = repo.unfiltered()
400 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
412 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
401 heads, roots, roots, heads)
413 heads, roots, roots, heads)
402 return [c.node() for c in revset]
414 return [c.node() for c in revset]
403
415
404
416
405 def newcommitphase(ui):
417 def newcommitphase(ui):
406 """helper to get the target phase of new commit
418 """helper to get the target phase of new commit
407
419
408 Handle all possible values for the phases.new-commit options.
420 Handle all possible values for the phases.new-commit options.
409
421
410 """
422 """
411 v = ui.config('phases', 'new-commit', draft)
423 v = ui.config('phases', 'new-commit', draft)
412 try:
424 try:
413 return phasenames.index(v)
425 return phasenames.index(v)
414 except ValueError:
426 except ValueError:
415 try:
427 try:
416 return int(v)
428 return int(v)
417 except ValueError:
429 except ValueError:
418 msg = _("phases.new-commit: not a valid phase name ('%s')")
430 msg = _("phases.new-commit: not a valid phase name ('%s')")
419 raise error.ConfigError(msg % v)
431 raise error.ConfigError(msg % v)
420
432
421 def hassecret(repo):
433 def hassecret(repo):
422 """utility function that check if a repo have any secret changeset."""
434 """utility function that check if a repo have any secret changeset."""
423 return bool(repo._phasecache.phaseroots[2])
435 return bool(repo._phasecache.phaseroots[2])
General Comments 0
You need to be logged in to leave comments. Login now