##// END OF EJS Templates
phases: fix typos in docstrings
Martin Geisler -
r16724:00535da8 default
parent child Browse files
Show More
@@ -1,420 +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 of the
9 This software may be used and distributed according to the terms of the
10 GNU General Public License version 2 or any later version.
10 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 phases' 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 below,
21 manipulated and communicated. The details of each phase is described below,
22 here we describe the properties they have in common.
22 here we describe the properties they have in common.
23
23
24 Like bookmarks, phases are not stored in history and thus are not permanent and
24 Like bookmarks, phases are not stored in history and thus are not permanent and
25 leave no audit trail.
25 leave no audit trail.
26
26
27 First, no changeset can be in two phases at once. Phases are ordered, so they
27 First, no changeset can be in two phases at once. Phases are ordered, so they
28 can be considered from lowest to highest. The default, lowest phase is 'public'
28 can be considered from lowest to highest. The default, lowest phase is 'public'
29 - this is the normal phase of existing changesets. A child changeset can not be
29 - this is the normal phase of existing changesets. A child changeset can not be
30 in a lower phase than its parents.
30 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 are exchanged by pushkey on pull and push. Some server have a
44 Phase data is exchanged by pushkey on pull and push. Some servers have a
45 publish option set, we call them publishing server. Pushing to such server make
45 publish option set, we call such a server a "publishing server". Pushing a
46 draft changeset publish.
46 draft changeset to a publishing server changes the phase to public.
47
47
48 A small list of fact/rules define the exchange of phase:
48 A small list of fact/rules define the exchange of phase:
49
49
50 * old client never changes server states
50 * old client never changes server states
51 * pull never changes server states
51 * pull never changes server states
52 * publish and old server csets are seen as public by client
52 * publish and old server changesets are seen as public by client
53 * any secret changeset seen in another repository is lowered to at least draft
53
54
54 * Any secret changeset seens in another repository is lowered to at least draft
55 Here is the final table summing up the 49 possible use cases of phase exchange:
55
56
57 Here is the final table summing up the 49 possible usecase of phase exchange:
58
56
59 server
57 server
60 old publish non-publish
58 old publish non-publish
61 N X N D P N D P
59 N X N D P N D P
62 old client
60 old client
63 pull
61 pull
64 N - X/X - X/D X/P - X/D X/P
62 N - X/X - X/D X/P - X/D X/P
65 X - X/X - X/D X/P - X/D X/P
63 X - X/X - X/D X/P - X/D X/P
66 push
64 push
67 X X/X X/X X/P X/P X/P X/D X/D X/P
65 X X/X X/X X/P X/P X/P X/D X/D X/P
68 new client
66 new client
69 pull
67 pull
70 N - P/X - P/D P/P - D/D P/P
68 N - P/X - P/D P/P - D/D P/P
71 D - P/X - P/D P/P - D/D P/P
69 D - P/X - P/D P/P - D/D P/P
72 P - P/X - P/D P/P - P/D P/P
70 P - P/X - P/D P/P - P/D P/P
73 push
71 push
74 D P/X P/X P/P P/P P/P D/D D/D P/P
72 D P/X P/X P/P P/P P/P D/D D/D P/P
75 P P/X P/X P/P P/P P/P P/P P/P P/P
73 P P/X P/X P/P P/P P/P P/P P/P P/P
76
74
77 Legend:
75 Legend:
78
76
79 A/B = final state on client / state on server
77 A/B = final state on client / state on server
80
78
81 * N = new/not present,
79 * N = new/not present,
82 * P = public,
80 * P = public,
83 * D = draft,
81 * D = draft,
84 * X = not tracked (ie: the old client or server has no internal way of
82 * X = not tracked (i.e., the old client or server has no internal way of
85 recording the phase.)
83 recording the phase.)
86
84
87 passive = only pushes
85 passive = only pushes
88
86
89
87
90 A cell here can be read like this:
88 A cell here can be read like this:
91
89
92 "When a new client pushes a draft changeset (D) to a publishing server
90 "When a new client pushes a draft changeset (D) to a publishing server
93 where it's not present (N), it's marked public on both sides (P/P)."
91 where it's not present (N), it's marked public on both sides (P/P)."
94
92
95 Note: old client behave as publish server with Draft only content
93 Note: old client behave as a publishing server with draft only content
96 - other people see it as public
94 - other people see it as public
97 - content is pushed as draft
95 - content is pushed as draft
98
96
99 """
97 """
100
98
101 import errno
99 import errno
102 from node import nullid, nullrev, bin, hex, short
100 from node import nullid, nullrev, bin, hex, short
103 from i18n import _
101 from i18n import _
104 import util
102 import util
105
103
106 allphases = public, draft, secret = range(3)
104 allphases = public, draft, secret = range(3)
107 trackedphases = allphases[1:]
105 trackedphases = allphases[1:]
108 phasenames = ['public', 'draft', 'secret']
106 phasenames = ['public', 'draft', 'secret']
109
107
110 def _filterunknown(ui, changelog, phaseroots):
108 def _filterunknown(ui, changelog, phaseroots):
111 """remove unknown nodes from the phase boundary
109 """remove unknown nodes from the phase boundary
112
110
113 Nothing is lost as unknown nodes only hold data for their descendants
111 Nothing is lost as unknown nodes only hold data for their descendants.
114 """
112 """
115 updated = False
113 updated = False
116 nodemap = changelog.nodemap # to filter unknown nodes
114 nodemap = changelog.nodemap # to filter unknown nodes
117 for phase, nodes in enumerate(phaseroots):
115 for phase, nodes in enumerate(phaseroots):
118 missing = [node for node in nodes if node not in nodemap]
116 missing = [node for node in nodes if node not in nodemap]
119 if missing:
117 if missing:
120 for mnode in missing:
118 for mnode in missing:
121 ui.debug(
119 ui.debug(
122 'removing unknown node %s from %i-phase boundary\n'
120 'removing unknown node %s from %i-phase boundary\n'
123 % (short(mnode), phase))
121 % (short(mnode), phase))
124 nodes.symmetric_difference_update(missing)
122 nodes.symmetric_difference_update(missing)
125 updated = True
123 updated = True
126 return updated
124 return updated
127
125
128 def _readroots(repo, phasedefaults=None):
126 def _readroots(repo, phasedefaults=None):
129 """Read phase roots from disk
127 """Read phase roots from disk
130
128
131 phasedefaults is a list of fn(repo, roots) callable, which are
129 phasedefaults is a list of fn(repo, roots) callable, which are
132 executed if the phase roots file does not exist. When phases are
130 executed if the phase roots file does not exist. When phases are
133 being initialized on an existing repository, this could be used to
131 being initialized on an existing repository, this could be used to
134 set selected changesets phase to something else than public.
132 set selected changesets phase to something else than public.
135
133
136 Return (roots, dirty) where dirty is true if roots differ from
134 Return (roots, dirty) where dirty is true if roots differ from
137 what is being stored.
135 what is being stored.
138 """
136 """
139 dirty = False
137 dirty = False
140 roots = [set() for i in allphases]
138 roots = [set() for i in allphases]
141 try:
139 try:
142 f = repo.sopener('phaseroots')
140 f = repo.sopener('phaseroots')
143 try:
141 try:
144 for line in f:
142 for line in f:
145 phase, nh = line.split()
143 phase, nh = line.split()
146 roots[int(phase)].add(bin(nh))
144 roots[int(phase)].add(bin(nh))
147 finally:
145 finally:
148 f.close()
146 f.close()
149 except IOError, inst:
147 except IOError, inst:
150 if inst.errno != errno.ENOENT:
148 if inst.errno != errno.ENOENT:
151 raise
149 raise
152 if phasedefaults:
150 if phasedefaults:
153 for f in phasedefaults:
151 for f in phasedefaults:
154 roots = f(repo, roots)
152 roots = f(repo, roots)
155 dirty = True
153 dirty = True
156 if _filterunknown(repo.ui, repo.changelog, roots):
154 if _filterunknown(repo.ui, repo.changelog, roots):
157 dirty = True
155 dirty = True
158 return roots, dirty
156 return roots, dirty
159
157
160 class phasecache(object):
158 class phasecache(object):
161 def __init__(self, repo, phasedefaults, _load=True):
159 def __init__(self, repo, phasedefaults, _load=True):
162 if _load:
160 if _load:
163 # Cheap trick to allow shallow-copy without copy module
161 # Cheap trick to allow shallow-copy without copy module
164 self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
162 self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
165 self.opener = repo.sopener
163 self.opener = repo.sopener
166 self._phaserevs = None
164 self._phaserevs = None
167
165
168 def copy(self):
166 def copy(self):
169 # Shallow copy meant to ensure isolation in
167 # Shallow copy meant to ensure isolation in
170 # advance/retractboundary(), nothing more.
168 # advance/retractboundary(), nothing more.
171 ph = phasecache(None, None, _load=False)
169 ph = phasecache(None, None, _load=False)
172 ph.phaseroots = self.phaseroots[:]
170 ph.phaseroots = self.phaseroots[:]
173 ph.dirty = self.dirty
171 ph.dirty = self.dirty
174 ph.opener = self.opener
172 ph.opener = self.opener
175 ph._phaserevs = self._phaserevs
173 ph._phaserevs = self._phaserevs
176 return ph
174 return ph
177
175
178 def replace(self, phcache):
176 def replace(self, phcache):
179 for a in 'phaseroots dirty opener _phaserevs'.split():
177 for a in 'phaseroots dirty opener _phaserevs'.split():
180 setattr(self, a, getattr(phcache, a))
178 setattr(self, a, getattr(phcache, a))
181
179
182 def getphaserevs(self, repo, rebuild=False):
180 def getphaserevs(self, repo, rebuild=False):
183 if rebuild or self._phaserevs is None:
181 if rebuild or self._phaserevs is None:
184 revs = [public] * len(repo.changelog)
182 revs = [public] * len(repo.changelog)
185 for phase in trackedphases:
183 for phase in trackedphases:
186 roots = map(repo.changelog.rev, self.phaseroots[phase])
184 roots = map(repo.changelog.rev, self.phaseroots[phase])
187 if roots:
185 if roots:
188 for rev in roots:
186 for rev in roots:
189 revs[rev] = phase
187 revs[rev] = phase
190 for rev in repo.changelog.descendants(*roots):
188 for rev in repo.changelog.descendants(*roots):
191 revs[rev] = phase
189 revs[rev] = phase
192 self._phaserevs = revs
190 self._phaserevs = revs
193 return self._phaserevs
191 return self._phaserevs
194
192
195 def phase(self, repo, rev):
193 def phase(self, repo, rev):
196 # We need a repo argument here to be able to build _phaserev
194 # We need a repo argument here to be able to build _phaserev
197 # if necessary. The repository instance is not stored in
195 # if necessary. The repository instance is not stored in
198 # phasecache to avoid reference cycles. The changelog instance
196 # phasecache to avoid reference cycles. The changelog instance
199 # is not stored because it is a filecache() property and can
197 # is not stored because it is a filecache() property and can
200 # be replaced without us being notified.
198 # be replaced without us being notified.
201 if rev == nullrev:
199 if rev == nullrev:
202 return public
200 return public
203 if self._phaserevs is None or rev >= len(self._phaserevs):
201 if self._phaserevs is None or rev >= len(self._phaserevs):
204 self._phaserevs = self.getphaserevs(repo, rebuild=True)
202 self._phaserevs = self.getphaserevs(repo, rebuild=True)
205 return self._phaserevs[rev]
203 return self._phaserevs[rev]
206
204
207 def write(self):
205 def write(self):
208 if not self.dirty:
206 if not self.dirty:
209 return
207 return
210 f = self.opener('phaseroots', 'w', atomictemp=True)
208 f = self.opener('phaseroots', 'w', atomictemp=True)
211 try:
209 try:
212 for phase, roots in enumerate(self.phaseroots):
210 for phase, roots in enumerate(self.phaseroots):
213 for h in roots:
211 for h in roots:
214 f.write('%i %s\n' % (phase, hex(h)))
212 f.write('%i %s\n' % (phase, hex(h)))
215 finally:
213 finally:
216 f.close()
214 f.close()
217 self.dirty = False
215 self.dirty = False
218
216
219 def _updateroots(self, phase, newroots):
217 def _updateroots(self, phase, newroots):
220 self.phaseroots[phase] = newroots
218 self.phaseroots[phase] = newroots
221 self._phaserevs = None
219 self._phaserevs = None
222 self.dirty = True
220 self.dirty = True
223
221
224 def advanceboundary(self, repo, targetphase, nodes):
222 def advanceboundary(self, repo, targetphase, nodes):
225 # Be careful to preserve shallow-copied values: do not update
223 # Be careful to preserve shallow-copied values: do not update
226 # phaseroots values, replace them.
224 # phaseroots values, replace them.
227
225
228 delroots = [] # set of root deleted by this path
226 delroots = [] # set of root deleted by this path
229 for phase in xrange(targetphase + 1, len(allphases)):
227 for phase in xrange(targetphase + 1, len(allphases)):
230 # filter nodes that are not in a compatible phase already
228 # filter nodes that are not in a compatible phase already
231 nodes = [n for n in nodes
229 nodes = [n for n in nodes
232 if self.phase(repo, repo[n].rev()) >= phase]
230 if self.phase(repo, repo[n].rev()) >= phase]
233 if not nodes:
231 if not nodes:
234 break # no roots to move anymore
232 break # no roots to move anymore
235 olds = self.phaseroots[phase]
233 olds = self.phaseroots[phase]
236 roots = set(ctx.node() for ctx in repo.set(
234 roots = set(ctx.node() for ctx in repo.set(
237 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
235 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
238 if olds != roots:
236 if olds != roots:
239 self._updateroots(phase, roots)
237 self._updateroots(phase, roots)
240 # some roots may need to be declared for lower phases
238 # some roots may need to be declared for lower phases
241 delroots.extend(olds - roots)
239 delroots.extend(olds - roots)
242 # declare deleted root in the target phase
240 # declare deleted root in the target phase
243 if targetphase != 0:
241 if targetphase != 0:
244 self.retractboundary(repo, targetphase, delroots)
242 self.retractboundary(repo, targetphase, delroots)
245
243
246 def retractboundary(self, repo, targetphase, nodes):
244 def retractboundary(self, repo, targetphase, nodes):
247 # Be careful to preserve shallow-copied values: do not update
245 # Be careful to preserve shallow-copied values: do not update
248 # phaseroots values, replace them.
246 # phaseroots values, replace them.
249
247
250 currentroots = self.phaseroots[targetphase]
248 currentroots = self.phaseroots[targetphase]
251 newroots = [n for n in nodes
249 newroots = [n for n in nodes
252 if self.phase(repo, repo[n].rev()) < targetphase]
250 if self.phase(repo, repo[n].rev()) < targetphase]
253 if newroots:
251 if newroots:
254 if nullid in newroots:
252 if nullid in newroots:
255 raise util.Abort(_('cannot change null revision phase'))
253 raise util.Abort(_('cannot change null revision phase'))
256 currentroots = currentroots.copy()
254 currentroots = currentroots.copy()
257 currentroots.update(newroots)
255 currentroots.update(newroots)
258 ctxs = repo.set('roots(%ln::)', currentroots)
256 ctxs = repo.set('roots(%ln::)', currentroots)
259 currentroots.intersection_update(ctx.node() for ctx in ctxs)
257 currentroots.intersection_update(ctx.node() for ctx in ctxs)
260 self._updateroots(targetphase, currentroots)
258 self._updateroots(targetphase, currentroots)
261
259
262 def advanceboundary(repo, targetphase, nodes):
260 def advanceboundary(repo, targetphase, nodes):
263 """Add nodes to a phase changing other nodes phases if necessary.
261 """Add nodes to a phase changing other nodes phases if necessary.
264
262
265 This function move boundary *forward* this means that all nodes are set
263 This function move boundary *forward* this means that all nodes are set
266 in the target phase or kept in a *lower* phase.
264 in the target phase or kept in a *lower* phase.
267
265
268 Simplify boundary to contains phase roots only."""
266 Simplify boundary to contains phase roots only."""
269 phcache = repo._phasecache.copy()
267 phcache = repo._phasecache.copy()
270 phcache.advanceboundary(repo, targetphase, nodes)
268 phcache.advanceboundary(repo, targetphase, nodes)
271 repo._phasecache.replace(phcache)
269 repo._phasecache.replace(phcache)
272
270
273 def retractboundary(repo, targetphase, nodes):
271 def retractboundary(repo, targetphase, nodes):
274 """Set nodes back to a phase changing other nodes phases if necessary.
272 """Set nodes back to a phase changing other nodes phases if necessary.
275
273
276 This function move boundary *backward* this means that all nodes are set
274 This function move boundary *backward* this means that all nodes are set
277 in the target phase or kept in a *higher* phase.
275 in the target phase or kept in a *higher* phase.
278
276
279 Simplify boundary to contains phase roots only."""
277 Simplify boundary to contains phase roots only."""
280 phcache = repo._phasecache.copy()
278 phcache = repo._phasecache.copy()
281 phcache.retractboundary(repo, targetphase, nodes)
279 phcache.retractboundary(repo, targetphase, nodes)
282 repo._phasecache.replace(phcache)
280 repo._phasecache.replace(phcache)
283
281
284 def listphases(repo):
282 def listphases(repo):
285 """List phases root for serialisation over pushkey"""
283 """List phases root for serialization over pushkey"""
286 keys = {}
284 keys = {}
287 value = '%i' % draft
285 value = '%i' % draft
288 for root in repo._phasecache.phaseroots[draft]:
286 for root in repo._phasecache.phaseroots[draft]:
289 keys[hex(root)] = value
287 keys[hex(root)] = value
290
288
291 if repo.ui.configbool('phases', 'publish', True):
289 if repo.ui.configbool('phases', 'publish', True):
292 # Add an extra data to let remote know we are a publishing repo.
290 # Add an extra data to let remote know we are a publishing repo.
293 # Publishing repo can't just pretend they are old repo. When pushing to
291 # Publishing repo can't just pretend they are old repo. When pushing to
294 # a publishing repo, the client still need to push phase boundary
292 # a publishing repo, the client still need to push phase boundary
295 #
293 #
296 # Push do not only push changeset. It also push phase data. New
294 # Push do not only push changeset. It also push phase data. New
297 # phase data may apply to common changeset which won't be push (as they
295 # phase data may apply to common changeset which won't be push (as they
298 # are common). Here is a very simple example:
296 # are common). Here is a very simple example:
299 #
297 #
300 # 1) repo A push changeset X as draft to repo B
298 # 1) repo A push changeset X as draft to repo B
301 # 2) repo B make changeset X public
299 # 2) repo B make changeset X public
302 # 3) repo B push to repo A. X is not pushed but the data that X as now
300 # 3) repo B push to repo A. X is not pushed but the data that X as now
303 # public should
301 # public should
304 #
302 #
305 # The server can't handle it on it's own as it has no idea of client
303 # The server can't handle it on it's own as it has no idea of client
306 # phase data.
304 # phase data.
307 keys['publishing'] = 'True'
305 keys['publishing'] = 'True'
308 return keys
306 return keys
309
307
310 def pushphase(repo, nhex, oldphasestr, newphasestr):
308 def pushphase(repo, nhex, oldphasestr, newphasestr):
311 """List phases root for serialisation over pushkey"""
309 """List phases root for serialisation over pushkey"""
312 lock = repo.lock()
310 lock = repo.lock()
313 try:
311 try:
314 currentphase = repo[nhex].phase()
312 currentphase = repo[nhex].phase()
315 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
313 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
316 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
314 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
317 if currentphase == oldphase and newphase < oldphase:
315 if currentphase == oldphase and newphase < oldphase:
318 advanceboundary(repo, newphase, [bin(nhex)])
316 advanceboundary(repo, newphase, [bin(nhex)])
319 return 1
317 return 1
320 elif currentphase == newphase:
318 elif currentphase == newphase:
321 # raced, but got correct result
319 # raced, but got correct result
322 return 1
320 return 1
323 else:
321 else:
324 return 0
322 return 0
325 finally:
323 finally:
326 lock.release()
324 lock.release()
327
325
328 def visibleheads(repo):
326 def visibleheads(repo):
329 """return the set of visible head of this repo"""
327 """return the set of visible head of this repo"""
330 # XXX we want a cache on this
328 # XXX we want a cache on this
331 sroots = repo._phasecache.phaseroots[secret]
329 sroots = repo._phasecache.phaseroots[secret]
332 if sroots:
330 if sroots:
333 # XXX very slow revset. storing heads or secret "boundary" would help.
331 # XXX very slow revset. storing heads or secret "boundary" would help.
334 revset = repo.set('heads(not (%ln::))', sroots)
332 revset = repo.set('heads(not (%ln::))', sroots)
335
333
336 vheads = [ctx.node() for ctx in revset]
334 vheads = [ctx.node() for ctx in revset]
337 if not vheads:
335 if not vheads:
338 vheads.append(nullid)
336 vheads.append(nullid)
339 else:
337 else:
340 vheads = repo.heads()
338 vheads = repo.heads()
341 return vheads
339 return vheads
342
340
343 def visiblebranchmap(repo):
341 def visiblebranchmap(repo):
344 """return a branchmap for the visible set"""
342 """return a branchmap for the visible set"""
345 # XXX Recomputing this data on the fly is very slow. We should build a
343 # XXX Recomputing this data on the fly is very slow. We should build a
346 # XXX cached version while computin the standard branchmap version.
344 # XXX cached version while computin the standard branchmap version.
347 sroots = repo._phasecache.phaseroots[secret]
345 sroots = repo._phasecache.phaseroots[secret]
348 if sroots:
346 if sroots:
349 vbranchmap = {}
347 vbranchmap = {}
350 for branch, nodes in repo.branchmap().iteritems():
348 for branch, nodes in repo.branchmap().iteritems():
351 # search for secret heads.
349 # search for secret heads.
352 for n in nodes:
350 for n in nodes:
353 if repo[n].phase() >= secret:
351 if repo[n].phase() >= secret:
354 nodes = None
352 nodes = None
355 break
353 break
356 # if secreat heads where found we must compute them again
354 # if secreat heads where found we must compute them again
357 if nodes is None:
355 if nodes is None:
358 s = repo.set('heads(branch(%s) - secret())', branch)
356 s = repo.set('heads(branch(%s) - secret())', branch)
359 nodes = [c.node() for c in s]
357 nodes = [c.node() for c in s]
360 vbranchmap[branch] = nodes
358 vbranchmap[branch] = nodes
361 else:
359 else:
362 vbranchmap = repo.branchmap()
360 vbranchmap = repo.branchmap()
363 return vbranchmap
361 return vbranchmap
364
362
365 def analyzeremotephases(repo, subset, roots):
363 def analyzeremotephases(repo, subset, roots):
366 """Compute phases heads and root in a subset of node from root dict
364 """Compute phases heads and root in a subset of node from root dict
367
365
368 * subset is heads of the subset
366 * subset is heads of the subset
369 * roots is {<nodeid> => phase} mapping. key and value are string.
367 * roots is {<nodeid> => phase} mapping. key and value are string.
370
368
371 Accept unknown element input
369 Accept unknown element input
372 """
370 """
373 # build list from dictionary
371 # build list from dictionary
374 draftroots = []
372 draftroots = []
375 nodemap = repo.changelog.nodemap # to filter unknown nodes
373 nodemap = repo.changelog.nodemap # to filter unknown nodes
376 for nhex, phase in roots.iteritems():
374 for nhex, phase in roots.iteritems():
377 if nhex == 'publishing': # ignore data related to publish option
375 if nhex == 'publishing': # ignore data related to publish option
378 continue
376 continue
379 node = bin(nhex)
377 node = bin(nhex)
380 phase = int(phase)
378 phase = int(phase)
381 if phase == 0:
379 if phase == 0:
382 if node != nullid:
380 if node != nullid:
383 repo.ui.warn(_('ignoring inconsistent public root'
381 repo.ui.warn(_('ignoring inconsistent public root'
384 ' from remote: %s\n') % nhex)
382 ' from remote: %s\n') % nhex)
385 elif phase == 1:
383 elif phase == 1:
386 if node in nodemap:
384 if node in nodemap:
387 draftroots.append(node)
385 draftroots.append(node)
388 else:
386 else:
389 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
387 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
390 % (phase, nhex))
388 % (phase, nhex))
391 # compute heads
389 # compute heads
392 publicheads = newheads(repo, subset, draftroots)
390 publicheads = newheads(repo, subset, draftroots)
393 return publicheads, draftroots
391 return publicheads, draftroots
394
392
395 def newheads(repo, heads, roots):
393 def newheads(repo, heads, roots):
396 """compute new head of a subset minus another
394 """compute new head of a subset minus another
397
395
398 * `heads`: define the first subset
396 * `heads`: define the first subset
399 * `rroots`: define the second we substract to the first"""
397 * `rroots`: define the second we substract to the first"""
400 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
398 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
401 heads, roots, roots, heads)
399 heads, roots, roots, heads)
402 return [c.node() for c in revset]
400 return [c.node() for c in revset]
403
401
404
402
405 def newcommitphase(ui):
403 def newcommitphase(ui):
406 """helper to get the target phase of new commit
404 """helper to get the target phase of new commit
407
405
408 Handle all possible values for the phases.new-commit options.
406 Handle all possible values for the phases.new-commit options.
409
407
410 """
408 """
411 v = ui.config('phases', 'new-commit', draft)
409 v = ui.config('phases', 'new-commit', draft)
412 try:
410 try:
413 return phasenames.index(v)
411 return phasenames.index(v)
414 except ValueError:
412 except ValueError:
415 try:
413 try:
416 return int(v)
414 return int(v)
417 except ValueError:
415 except ValueError:
418 msg = _("phases.new-commit: not a valid phase name ('%s')")
416 msg = _("phases.new-commit: not a valid phase name ('%s')")
419 raise error.ConfigError(msg % v)
417 raise error.ConfigError(msg % v)
420
418
General Comments 0
You need to be logged in to leave comments. Login now