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