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