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