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