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