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