##// END OF EJS Templates
phases: check HG_PENDING strictly...
FUJIWARA Katsunori -
r31053:6afd8a87 default
parent child Browse files
Show More
@@ -1,503 +1,495
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 from __future__ import absolute_import
103 from __future__ import absolute_import
104
104
105 import errno
105 import errno
106
106
107 from .i18n import _
107 from .i18n import _
108 from .node import (
108 from .node import (
109 bin,
109 bin,
110 hex,
110 hex,
111 nullid,
111 nullid,
112 nullrev,
112 nullrev,
113 short,
113 short,
114 )
114 )
115 from . import (
115 from . import (
116 encoding,
117 error,
116 error,
118 smartset,
117 smartset,
118 txnutil,
119 )
119 )
120
120
121 allphases = public, draft, secret = range(3)
121 allphases = public, draft, secret = range(3)
122 trackedphases = allphases[1:]
122 trackedphases = allphases[1:]
123 phasenames = ['public', 'draft', 'secret']
123 phasenames = ['public', 'draft', 'secret']
124
124
125 def _readroots(repo, phasedefaults=None):
125 def _readroots(repo, phasedefaults=None):
126 """Read phase roots from disk
126 """Read phase roots from disk
127
127
128 phasedefaults is a list of fn(repo, roots) callable, which are
128 phasedefaults is a list of fn(repo, roots) callable, which are
129 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
130 being initialized on an existing repository, this could be used to
130 being initialized on an existing repository, this could be used to
131 set selected changesets phase to something else than public.
131 set selected changesets phase to something else than public.
132
132
133 Return (roots, dirty) where dirty is true if roots differ from
133 Return (roots, dirty) where dirty is true if roots differ from
134 what is being stored.
134 what is being stored.
135 """
135 """
136 repo = repo.unfiltered()
136 repo = repo.unfiltered()
137 dirty = False
137 dirty = False
138 roots = [set() for i in allphases]
138 roots = [set() for i in allphases]
139 try:
139 try:
140 f = None
140 f, pending = txnutil.trypending(repo.root, repo.svfs, 'phaseroots')
141 if 'HG_PENDING' in encoding.environ:
142 try:
143 f = repo.svfs('phaseroots.pending')
144 except IOError as inst:
145 if inst.errno != errno.ENOENT:
146 raise
147 if f is None:
148 f = repo.svfs('phaseroots')
149 try:
141 try:
150 for line in f:
142 for line in f:
151 phase, nh = line.split()
143 phase, nh = line.split()
152 roots[int(phase)].add(bin(nh))
144 roots[int(phase)].add(bin(nh))
153 finally:
145 finally:
154 f.close()
146 f.close()
155 except IOError as inst:
147 except IOError as inst:
156 if inst.errno != errno.ENOENT:
148 if inst.errno != errno.ENOENT:
157 raise
149 raise
158 if phasedefaults:
150 if phasedefaults:
159 for f in phasedefaults:
151 for f in phasedefaults:
160 roots = f(repo, roots)
152 roots = f(repo, roots)
161 dirty = True
153 dirty = True
162 return roots, dirty
154 return roots, dirty
163
155
164 class phasecache(object):
156 class phasecache(object):
165 def __init__(self, repo, phasedefaults, _load=True):
157 def __init__(self, repo, phasedefaults, _load=True):
166 if _load:
158 if _load:
167 # Cheap trick to allow shallow-copy without copy module
159 # Cheap trick to allow shallow-copy without copy module
168 self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
160 self.phaseroots, self.dirty = _readroots(repo, phasedefaults)
169 self._phaserevs = None
161 self._phaserevs = None
170 self._phasesets = None
162 self._phasesets = None
171 self.filterunknown(repo)
163 self.filterunknown(repo)
172 self.opener = repo.svfs
164 self.opener = repo.svfs
173
165
174 def getrevset(self, repo, phases):
166 def getrevset(self, repo, phases):
175 """return a smartset for the given phases"""
167 """return a smartset for the given phases"""
176 self.loadphaserevs(repo) # ensure phase's sets are loaded
168 self.loadphaserevs(repo) # ensure phase's sets are loaded
177
169
178 if self._phasesets and all(self._phasesets[p] is not None
170 if self._phasesets and all(self._phasesets[p] is not None
179 for p in phases):
171 for p in phases):
180 # fast path - use _phasesets
172 # fast path - use _phasesets
181 revs = self._phasesets[phases[0]]
173 revs = self._phasesets[phases[0]]
182 if len(phases) > 1:
174 if len(phases) > 1:
183 revs = revs.copy() # only copy when needed
175 revs = revs.copy() # only copy when needed
184 for p in phases[1:]:
176 for p in phases[1:]:
185 revs.update(self._phasesets[p])
177 revs.update(self._phasesets[p])
186 if repo.changelog.filteredrevs:
178 if repo.changelog.filteredrevs:
187 revs = revs - repo.changelog.filteredrevs
179 revs = revs - repo.changelog.filteredrevs
188 return smartset.baseset(revs)
180 return smartset.baseset(revs)
189 else:
181 else:
190 # slow path - enumerate all revisions
182 # slow path - enumerate all revisions
191 phase = self.phase
183 phase = self.phase
192 revs = (r for r in repo if phase(repo, r) in phases)
184 revs = (r for r in repo if phase(repo, r) in phases)
193 return smartset.generatorset(revs, iterasc=True)
185 return smartset.generatorset(revs, iterasc=True)
194
186
195 def copy(self):
187 def copy(self):
196 # Shallow copy meant to ensure isolation in
188 # Shallow copy meant to ensure isolation in
197 # advance/retractboundary(), nothing more.
189 # advance/retractboundary(), nothing more.
198 ph = self.__class__(None, None, _load=False)
190 ph = self.__class__(None, None, _load=False)
199 ph.phaseroots = self.phaseroots[:]
191 ph.phaseroots = self.phaseroots[:]
200 ph.dirty = self.dirty
192 ph.dirty = self.dirty
201 ph.opener = self.opener
193 ph.opener = self.opener
202 ph._phaserevs = self._phaserevs
194 ph._phaserevs = self._phaserevs
203 ph._phasesets = self._phasesets
195 ph._phasesets = self._phasesets
204 return ph
196 return ph
205
197
206 def replace(self, phcache):
198 def replace(self, phcache):
207 """replace all values in 'self' with content of phcache"""
199 """replace all values in 'self' with content of phcache"""
208 for a in ('phaseroots', 'dirty', 'opener', '_phaserevs', '_phasesets'):
200 for a in ('phaseroots', 'dirty', 'opener', '_phaserevs', '_phasesets'):
209 setattr(self, a, getattr(phcache, a))
201 setattr(self, a, getattr(phcache, a))
210
202
211 def _getphaserevsnative(self, repo):
203 def _getphaserevsnative(self, repo):
212 repo = repo.unfiltered()
204 repo = repo.unfiltered()
213 nativeroots = []
205 nativeroots = []
214 for phase in trackedphases:
206 for phase in trackedphases:
215 nativeroots.append(map(repo.changelog.rev, self.phaseroots[phase]))
207 nativeroots.append(map(repo.changelog.rev, self.phaseroots[phase]))
216 return repo.changelog.computephases(nativeroots)
208 return repo.changelog.computephases(nativeroots)
217
209
218 def _computephaserevspure(self, repo):
210 def _computephaserevspure(self, repo):
219 repo = repo.unfiltered()
211 repo = repo.unfiltered()
220 revs = [public] * len(repo.changelog)
212 revs = [public] * len(repo.changelog)
221 self._phaserevs = revs
213 self._phaserevs = revs
222 self._populatephaseroots(repo)
214 self._populatephaseroots(repo)
223 for phase in trackedphases:
215 for phase in trackedphases:
224 roots = map(repo.changelog.rev, self.phaseroots[phase])
216 roots = map(repo.changelog.rev, self.phaseroots[phase])
225 if roots:
217 if roots:
226 for rev in roots:
218 for rev in roots:
227 revs[rev] = phase
219 revs[rev] = phase
228 for rev in repo.changelog.descendants(roots):
220 for rev in repo.changelog.descendants(roots):
229 revs[rev] = phase
221 revs[rev] = phase
230
222
231 def loadphaserevs(self, repo):
223 def loadphaserevs(self, repo):
232 """ensure phase information is loaded in the object"""
224 """ensure phase information is loaded in the object"""
233 if self._phaserevs is None:
225 if self._phaserevs is None:
234 try:
226 try:
235 if repo.ui.configbool('experimental',
227 if repo.ui.configbool('experimental',
236 'nativephaseskillswitch'):
228 'nativephaseskillswitch'):
237 self._computephaserevspure(repo)
229 self._computephaserevspure(repo)
238 else:
230 else:
239 res = self._getphaserevsnative(repo)
231 res = self._getphaserevsnative(repo)
240 self._phaserevs, self._phasesets = res
232 self._phaserevs, self._phasesets = res
241 except AttributeError:
233 except AttributeError:
242 self._computephaserevspure(repo)
234 self._computephaserevspure(repo)
243
235
244 def invalidate(self):
236 def invalidate(self):
245 self._phaserevs = None
237 self._phaserevs = None
246 self._phasesets = None
238 self._phasesets = None
247
239
248 def _populatephaseroots(self, repo):
240 def _populatephaseroots(self, repo):
249 """Fills the _phaserevs cache with phases for the roots.
241 """Fills the _phaserevs cache with phases for the roots.
250 """
242 """
251 cl = repo.changelog
243 cl = repo.changelog
252 phaserevs = self._phaserevs
244 phaserevs = self._phaserevs
253 for phase in trackedphases:
245 for phase in trackedphases:
254 roots = map(cl.rev, self.phaseroots[phase])
246 roots = map(cl.rev, self.phaseroots[phase])
255 for root in roots:
247 for root in roots:
256 phaserevs[root] = phase
248 phaserevs[root] = phase
257
249
258 def phase(self, repo, rev):
250 def phase(self, repo, rev):
259 # We need a repo argument here to be able to build _phaserevs
251 # We need a repo argument here to be able to build _phaserevs
260 # if necessary. The repository instance is not stored in
252 # if necessary. The repository instance is not stored in
261 # phasecache to avoid reference cycles. The changelog instance
253 # phasecache to avoid reference cycles. The changelog instance
262 # is not stored because it is a filecache() property and can
254 # is not stored because it is a filecache() property and can
263 # be replaced without us being notified.
255 # be replaced without us being notified.
264 if rev == nullrev:
256 if rev == nullrev:
265 return public
257 return public
266 if rev < nullrev:
258 if rev < nullrev:
267 raise ValueError(_('cannot lookup negative revision'))
259 raise ValueError(_('cannot lookup negative revision'))
268 if self._phaserevs is None or rev >= len(self._phaserevs):
260 if self._phaserevs is None or rev >= len(self._phaserevs):
269 self.invalidate()
261 self.invalidate()
270 self.loadphaserevs(repo)
262 self.loadphaserevs(repo)
271 return self._phaserevs[rev]
263 return self._phaserevs[rev]
272
264
273 def write(self):
265 def write(self):
274 if not self.dirty:
266 if not self.dirty:
275 return
267 return
276 f = self.opener('phaseroots', 'w', atomictemp=True, checkambig=True)
268 f = self.opener('phaseroots', 'w', atomictemp=True, checkambig=True)
277 try:
269 try:
278 self._write(f)
270 self._write(f)
279 finally:
271 finally:
280 f.close()
272 f.close()
281
273
282 def _write(self, fp):
274 def _write(self, fp):
283 for phase, roots in enumerate(self.phaseroots):
275 for phase, roots in enumerate(self.phaseroots):
284 for h in roots:
276 for h in roots:
285 fp.write('%i %s\n' % (phase, hex(h)))
277 fp.write('%i %s\n' % (phase, hex(h)))
286 self.dirty = False
278 self.dirty = False
287
279
288 def _updateroots(self, phase, newroots, tr):
280 def _updateroots(self, phase, newroots, tr):
289 self.phaseroots[phase] = newroots
281 self.phaseroots[phase] = newroots
290 self.invalidate()
282 self.invalidate()
291 self.dirty = True
283 self.dirty = True
292
284
293 tr.addfilegenerator('phase', ('phaseroots',), self._write)
285 tr.addfilegenerator('phase', ('phaseroots',), self._write)
294 tr.hookargs['phases_moved'] = '1'
286 tr.hookargs['phases_moved'] = '1'
295
287
296 def advanceboundary(self, repo, tr, targetphase, nodes):
288 def advanceboundary(self, repo, tr, targetphase, nodes):
297 # Be careful to preserve shallow-copied values: do not update
289 # Be careful to preserve shallow-copied values: do not update
298 # phaseroots values, replace them.
290 # phaseroots values, replace them.
299
291
300 repo = repo.unfiltered()
292 repo = repo.unfiltered()
301 delroots = [] # set of root deleted by this path
293 delroots = [] # set of root deleted by this path
302 for phase in xrange(targetphase + 1, len(allphases)):
294 for phase in xrange(targetphase + 1, len(allphases)):
303 # filter nodes that are not in a compatible phase already
295 # filter nodes that are not in a compatible phase already
304 nodes = [n for n in nodes
296 nodes = [n for n in nodes
305 if self.phase(repo, repo[n].rev()) >= phase]
297 if self.phase(repo, repo[n].rev()) >= phase]
306 if not nodes:
298 if not nodes:
307 break # no roots to move anymore
299 break # no roots to move anymore
308 olds = self.phaseroots[phase]
300 olds = self.phaseroots[phase]
309 roots = set(ctx.node() for ctx in repo.set(
301 roots = set(ctx.node() for ctx in repo.set(
310 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
302 'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
311 if olds != roots:
303 if olds != roots:
312 self._updateroots(phase, roots, tr)
304 self._updateroots(phase, roots, tr)
313 # some roots may need to be declared for lower phases
305 # some roots may need to be declared for lower phases
314 delroots.extend(olds - roots)
306 delroots.extend(olds - roots)
315 # declare deleted root in the target phase
307 # declare deleted root in the target phase
316 if targetphase != 0:
308 if targetphase != 0:
317 self.retractboundary(repo, tr, targetphase, delroots)
309 self.retractboundary(repo, tr, targetphase, delroots)
318 repo.invalidatevolatilesets()
310 repo.invalidatevolatilesets()
319
311
320 def retractboundary(self, repo, tr, targetphase, nodes):
312 def retractboundary(self, repo, tr, targetphase, nodes):
321 # Be careful to preserve shallow-copied values: do not update
313 # Be careful to preserve shallow-copied values: do not update
322 # phaseroots values, replace them.
314 # phaseroots values, replace them.
323
315
324 repo = repo.unfiltered()
316 repo = repo.unfiltered()
325 currentroots = self.phaseroots[targetphase]
317 currentroots = self.phaseroots[targetphase]
326 newroots = [n for n in nodes
318 newroots = [n for n in nodes
327 if self.phase(repo, repo[n].rev()) < targetphase]
319 if self.phase(repo, repo[n].rev()) < targetphase]
328 if newroots:
320 if newroots:
329 if nullid in newroots:
321 if nullid in newroots:
330 raise error.Abort(_('cannot change null revision phase'))
322 raise error.Abort(_('cannot change null revision phase'))
331 currentroots = currentroots.copy()
323 currentroots = currentroots.copy()
332 currentroots.update(newroots)
324 currentroots.update(newroots)
333
325
334 # Only compute new roots for revs above the roots that are being
326 # Only compute new roots for revs above the roots that are being
335 # retracted.
327 # retracted.
336 minnewroot = min(repo[n].rev() for n in newroots)
328 minnewroot = min(repo[n].rev() for n in newroots)
337 aboveroots = [n for n in currentroots
329 aboveroots = [n for n in currentroots
338 if repo[n].rev() >= minnewroot]
330 if repo[n].rev() >= minnewroot]
339 updatedroots = repo.set('roots(%ln::)', aboveroots)
331 updatedroots = repo.set('roots(%ln::)', aboveroots)
340
332
341 finalroots = set(n for n in currentroots if repo[n].rev() <
333 finalroots = set(n for n in currentroots if repo[n].rev() <
342 minnewroot)
334 minnewroot)
343 finalroots.update(ctx.node() for ctx in updatedroots)
335 finalroots.update(ctx.node() for ctx in updatedroots)
344
336
345 self._updateroots(targetphase, finalroots, tr)
337 self._updateroots(targetphase, finalroots, tr)
346 repo.invalidatevolatilesets()
338 repo.invalidatevolatilesets()
347
339
348 def filterunknown(self, repo):
340 def filterunknown(self, repo):
349 """remove unknown nodes from the phase boundary
341 """remove unknown nodes from the phase boundary
350
342
351 Nothing is lost as unknown nodes only hold data for their descendants.
343 Nothing is lost as unknown nodes only hold data for their descendants.
352 """
344 """
353 filtered = False
345 filtered = False
354 nodemap = repo.changelog.nodemap # to filter unknown nodes
346 nodemap = repo.changelog.nodemap # to filter unknown nodes
355 for phase, nodes in enumerate(self.phaseroots):
347 for phase, nodes in enumerate(self.phaseroots):
356 missing = sorted(node for node in nodes if node not in nodemap)
348 missing = sorted(node for node in nodes if node not in nodemap)
357 if missing:
349 if missing:
358 for mnode in missing:
350 for mnode in missing:
359 repo.ui.debug(
351 repo.ui.debug(
360 'removing unknown node %s from %i-phase boundary\n'
352 'removing unknown node %s from %i-phase boundary\n'
361 % (short(mnode), phase))
353 % (short(mnode), phase))
362 nodes.symmetric_difference_update(missing)
354 nodes.symmetric_difference_update(missing)
363 filtered = True
355 filtered = True
364 if filtered:
356 if filtered:
365 self.dirty = True
357 self.dirty = True
366 # filterunknown is called by repo.destroyed, we may have no changes in
358 # filterunknown is called by repo.destroyed, we may have no changes in
367 # root but phaserevs contents is certainly invalid (or at least we
359 # root but phaserevs contents is certainly invalid (or at least we
368 # have not proper way to check that). related to issue 3858.
360 # have not proper way to check that). related to issue 3858.
369 #
361 #
370 # The other caller is __init__ that have no _phaserevs initialized
362 # The other caller is __init__ that have no _phaserevs initialized
371 # anyway. If this change we should consider adding a dedicated
363 # anyway. If this change we should consider adding a dedicated
372 # "destroyed" function to phasecache or a proper cache key mechanism
364 # "destroyed" function to phasecache or a proper cache key mechanism
373 # (see branchmap one)
365 # (see branchmap one)
374 self.invalidate()
366 self.invalidate()
375
367
376 def advanceboundary(repo, tr, targetphase, nodes):
368 def advanceboundary(repo, tr, targetphase, nodes):
377 """Add nodes to a phase changing other nodes phases if necessary.
369 """Add nodes to a phase changing other nodes phases if necessary.
378
370
379 This function move boundary *forward* this means that all nodes
371 This function move boundary *forward* this means that all nodes
380 are set in the target phase or kept in a *lower* phase.
372 are set in the target phase or kept in a *lower* phase.
381
373
382 Simplify boundary to contains phase roots only."""
374 Simplify boundary to contains phase roots only."""
383 phcache = repo._phasecache.copy()
375 phcache = repo._phasecache.copy()
384 phcache.advanceboundary(repo, tr, targetphase, nodes)
376 phcache.advanceboundary(repo, tr, targetphase, nodes)
385 repo._phasecache.replace(phcache)
377 repo._phasecache.replace(phcache)
386
378
387 def retractboundary(repo, tr, targetphase, nodes):
379 def retractboundary(repo, tr, targetphase, nodes):
388 """Set nodes back to a phase changing other nodes phases if
380 """Set nodes back to a phase changing other nodes phases if
389 necessary.
381 necessary.
390
382
391 This function move boundary *backward* this means that all nodes
383 This function move boundary *backward* this means that all nodes
392 are set in the target phase or kept in a *higher* phase.
384 are set in the target phase or kept in a *higher* phase.
393
385
394 Simplify boundary to contains phase roots only."""
386 Simplify boundary to contains phase roots only."""
395 phcache = repo._phasecache.copy()
387 phcache = repo._phasecache.copy()
396 phcache.retractboundary(repo, tr, targetphase, nodes)
388 phcache.retractboundary(repo, tr, targetphase, nodes)
397 repo._phasecache.replace(phcache)
389 repo._phasecache.replace(phcache)
398
390
399 def listphases(repo):
391 def listphases(repo):
400 """List phases root for serialization over pushkey"""
392 """List phases root for serialization over pushkey"""
401 keys = {}
393 keys = {}
402 value = '%i' % draft
394 value = '%i' % draft
403 for root in repo._phasecache.phaseroots[draft]:
395 for root in repo._phasecache.phaseroots[draft]:
404 keys[hex(root)] = value
396 keys[hex(root)] = value
405
397
406 if repo.publishing():
398 if repo.publishing():
407 # Add an extra data to let remote know we are a publishing
399 # Add an extra data to let remote know we are a publishing
408 # repo. Publishing repo can't just pretend they are old repo.
400 # repo. Publishing repo can't just pretend they are old repo.
409 # When pushing to a publishing repo, the client still need to
401 # When pushing to a publishing repo, the client still need to
410 # push phase boundary
402 # push phase boundary
411 #
403 #
412 # Push do not only push changeset. It also push phase data.
404 # Push do not only push changeset. It also push phase data.
413 # New phase data may apply to common changeset which won't be
405 # New phase data may apply to common changeset which won't be
414 # push (as they are common). Here is a very simple example:
406 # push (as they are common). Here is a very simple example:
415 #
407 #
416 # 1) repo A push changeset X as draft to repo B
408 # 1) repo A push changeset X as draft to repo B
417 # 2) repo B make changeset X public
409 # 2) repo B make changeset X public
418 # 3) repo B push to repo A. X is not pushed but the data that
410 # 3) repo B push to repo A. X is not pushed but the data that
419 # X as now public should
411 # X as now public should
420 #
412 #
421 # The server can't handle it on it's own as it has no idea of
413 # The server can't handle it on it's own as it has no idea of
422 # client phase data.
414 # client phase data.
423 keys['publishing'] = 'True'
415 keys['publishing'] = 'True'
424 return keys
416 return keys
425
417
426 def pushphase(repo, nhex, oldphasestr, newphasestr):
418 def pushphase(repo, nhex, oldphasestr, newphasestr):
427 """List phases root for serialization over pushkey"""
419 """List phases root for serialization over pushkey"""
428 repo = repo.unfiltered()
420 repo = repo.unfiltered()
429 with repo.lock():
421 with repo.lock():
430 currentphase = repo[nhex].phase()
422 currentphase = repo[nhex].phase()
431 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
423 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
432 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
424 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
433 if currentphase == oldphase and newphase < oldphase:
425 if currentphase == oldphase and newphase < oldphase:
434 with repo.transaction('pushkey-phase') as tr:
426 with repo.transaction('pushkey-phase') as tr:
435 advanceboundary(repo, tr, newphase, [bin(nhex)])
427 advanceboundary(repo, tr, newphase, [bin(nhex)])
436 return 1
428 return 1
437 elif currentphase == newphase:
429 elif currentphase == newphase:
438 # raced, but got correct result
430 # raced, but got correct result
439 return 1
431 return 1
440 else:
432 else:
441 return 0
433 return 0
442
434
443 def analyzeremotephases(repo, subset, roots):
435 def analyzeremotephases(repo, subset, roots):
444 """Compute phases heads and root in a subset of node from root dict
436 """Compute phases heads and root in a subset of node from root dict
445
437
446 * subset is heads of the subset
438 * subset is heads of the subset
447 * roots is {<nodeid> => phase} mapping. key and value are string.
439 * roots is {<nodeid> => phase} mapping. key and value are string.
448
440
449 Accept unknown element input
441 Accept unknown element input
450 """
442 """
451 repo = repo.unfiltered()
443 repo = repo.unfiltered()
452 # build list from dictionary
444 # build list from dictionary
453 draftroots = []
445 draftroots = []
454 nodemap = repo.changelog.nodemap # to filter unknown nodes
446 nodemap = repo.changelog.nodemap # to filter unknown nodes
455 for nhex, phase in roots.iteritems():
447 for nhex, phase in roots.iteritems():
456 if nhex == 'publishing': # ignore data related to publish option
448 if nhex == 'publishing': # ignore data related to publish option
457 continue
449 continue
458 node = bin(nhex)
450 node = bin(nhex)
459 phase = int(phase)
451 phase = int(phase)
460 if phase == public:
452 if phase == public:
461 if node != nullid:
453 if node != nullid:
462 repo.ui.warn(_('ignoring inconsistent public root'
454 repo.ui.warn(_('ignoring inconsistent public root'
463 ' from remote: %s\n') % nhex)
455 ' from remote: %s\n') % nhex)
464 elif phase == draft:
456 elif phase == draft:
465 if node in nodemap:
457 if node in nodemap:
466 draftroots.append(node)
458 draftroots.append(node)
467 else:
459 else:
468 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
460 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
469 % (phase, nhex))
461 % (phase, nhex))
470 # compute heads
462 # compute heads
471 publicheads = newheads(repo, subset, draftroots)
463 publicheads = newheads(repo, subset, draftroots)
472 return publicheads, draftroots
464 return publicheads, draftroots
473
465
474 def newheads(repo, heads, roots):
466 def newheads(repo, heads, roots):
475 """compute new head of a subset minus another
467 """compute new head of a subset minus another
476
468
477 * `heads`: define the first subset
469 * `heads`: define the first subset
478 * `roots`: define the second we subtract from the first"""
470 * `roots`: define the second we subtract from the first"""
479 repo = repo.unfiltered()
471 repo = repo.unfiltered()
480 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
472 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
481 heads, roots, roots, heads)
473 heads, roots, roots, heads)
482 return [c.node() for c in revset]
474 return [c.node() for c in revset]
483
475
484
476
485 def newcommitphase(ui):
477 def newcommitphase(ui):
486 """helper to get the target phase of new commit
478 """helper to get the target phase of new commit
487
479
488 Handle all possible values for the phases.new-commit options.
480 Handle all possible values for the phases.new-commit options.
489
481
490 """
482 """
491 v = ui.config('phases', 'new-commit', draft)
483 v = ui.config('phases', 'new-commit', draft)
492 try:
484 try:
493 return phasenames.index(v)
485 return phasenames.index(v)
494 except ValueError:
486 except ValueError:
495 try:
487 try:
496 return int(v)
488 return int(v)
497 except ValueError:
489 except ValueError:
498 msg = _("phases.new-commit: not a valid phase name ('%s')")
490 msg = _("phases.new-commit: not a valid phase name ('%s')")
499 raise error.ConfigError(msg % v)
491 raise error.ConfigError(msg % v)
500
492
501 def hassecret(repo):
493 def hassecret(repo):
502 """utility function that check if a repo have any secret changeset."""
494 """utility function that check if a repo have any secret changeset."""
503 return bool(repo._phasecache.phaseroots[2])
495 return bool(repo._phasecache.phaseroots[2])
@@ -1,592 +1,636
1 $ hglog() { hg log --template "{rev} {phaseidx} {desc}\n" $*; }
1 $ hglog() { hg log --template "{rev} {phaseidx} {desc}\n" $*; }
2 $ mkcommit() {
2 $ mkcommit() {
3 > echo "$1" > "$1"
3 > echo "$1" > "$1"
4 > hg add "$1"
4 > hg add "$1"
5 > message="$1"
5 > message="$1"
6 > shift
6 > shift
7 > hg ci -m "$message" $*
7 > hg ci -m "$message" $*
8 > }
8 > }
9
9
10 $ hg init initialrepo
10 $ hg init initialrepo
11 $ cd initialrepo
11 $ cd initialrepo
12
12
13 Cannot change null revision phase
13 Cannot change null revision phase
14
14
15 $ hg phase --force --secret null
15 $ hg phase --force --secret null
16 abort: cannot change null revision phase
16 abort: cannot change null revision phase
17 [255]
17 [255]
18 $ hg phase null
18 $ hg phase null
19 -1: public
19 -1: public
20
20
21 $ mkcommit A
21 $ mkcommit A
22
22
23 New commit are draft by default
23 New commit are draft by default
24
24
25 $ hglog
25 $ hglog
26 0 1 A
26 0 1 A
27
27
28 Following commit are draft too
28 Following commit are draft too
29
29
30 $ mkcommit B
30 $ mkcommit B
31
31
32 $ hglog
32 $ hglog
33 1 1 B
33 1 1 B
34 0 1 A
34 0 1 A
35
35
36 Draft commit are properly created over public one:
36 Draft commit are properly created over public one:
37
37
38 $ hg phase --public .
38 $ hg phase --public .
39 $ hg phase
39 $ hg phase
40 1: public
40 1: public
41 $ hglog
41 $ hglog
42 1 0 B
42 1 0 B
43 0 0 A
43 0 0 A
44
44
45 $ mkcommit C
45 $ mkcommit C
46 $ mkcommit D
46 $ mkcommit D
47
47
48 $ hglog
48 $ hglog
49 3 1 D
49 3 1 D
50 2 1 C
50 2 1 C
51 1 0 B
51 1 0 B
52 0 0 A
52 0 0 A
53
53
54 Test creating changeset as secret
54 Test creating changeset as secret
55
55
56 $ mkcommit E --config phases.new-commit='secret'
56 $ mkcommit E --config phases.new-commit='secret'
57 $ hglog
57 $ hglog
58 4 2 E
58 4 2 E
59 3 1 D
59 3 1 D
60 2 1 C
60 2 1 C
61 1 0 B
61 1 0 B
62 0 0 A
62 0 0 A
63
63
64 Test the secret property is inherited
64 Test the secret property is inherited
65
65
66 $ mkcommit H
66 $ mkcommit H
67 $ hglog
67 $ hglog
68 5 2 H
68 5 2 H
69 4 2 E
69 4 2 E
70 3 1 D
70 3 1 D
71 2 1 C
71 2 1 C
72 1 0 B
72 1 0 B
73 0 0 A
73 0 0 A
74
74
75 Even on merge
75 Even on merge
76
76
77 $ hg up -q 1
77 $ hg up -q 1
78 $ mkcommit "B'"
78 $ mkcommit "B'"
79 created new head
79 created new head
80 $ hglog
80 $ hglog
81 6 1 B'
81 6 1 B'
82 5 2 H
82 5 2 H
83 4 2 E
83 4 2 E
84 3 1 D
84 3 1 D
85 2 1 C
85 2 1 C
86 1 0 B
86 1 0 B
87 0 0 A
87 0 0 A
88 $ hg merge 4 # E
88 $ hg merge 4 # E
89 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
89 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 (branch merge, don't forget to commit)
90 (branch merge, don't forget to commit)
91 $ hg phase
91 $ hg phase
92 6: draft
92 6: draft
93 4: secret
93 4: secret
94 $ hg ci -m "merge B' and E"
94 $ hg ci -m "merge B' and E"
95 $ hglog
95 $ hglog
96 7 2 merge B' and E
96 7 2 merge B' and E
97 6 1 B'
97 6 1 B'
98 5 2 H
98 5 2 H
99 4 2 E
99 4 2 E
100 3 1 D
100 3 1 D
101 2 1 C
101 2 1 C
102 1 0 B
102 1 0 B
103 0 0 A
103 0 0 A
104
104
105 Test secret changeset are not pushed
105 Test secret changeset are not pushed
106
106
107 $ hg init ../push-dest
107 $ hg init ../push-dest
108 $ cat > ../push-dest/.hg/hgrc << EOF
108 $ cat > ../push-dest/.hg/hgrc << EOF
109 > [phases]
109 > [phases]
110 > publish=False
110 > publish=False
111 > EOF
111 > EOF
112 $ hg outgoing ../push-dest --template='{rev} {phase} {desc|firstline}\n'
112 $ hg outgoing ../push-dest --template='{rev} {phase} {desc|firstline}\n'
113 comparing with ../push-dest
113 comparing with ../push-dest
114 searching for changes
114 searching for changes
115 0 public A
115 0 public A
116 1 public B
116 1 public B
117 2 draft C
117 2 draft C
118 3 draft D
118 3 draft D
119 6 draft B'
119 6 draft B'
120 $ hg outgoing -r 'branch(default)' ../push-dest --template='{rev} {phase} {desc|firstline}\n'
120 $ hg outgoing -r 'branch(default)' ../push-dest --template='{rev} {phase} {desc|firstline}\n'
121 comparing with ../push-dest
121 comparing with ../push-dest
122 searching for changes
122 searching for changes
123 0 public A
123 0 public A
124 1 public B
124 1 public B
125 2 draft C
125 2 draft C
126 3 draft D
126 3 draft D
127 6 draft B'
127 6 draft B'
128
128
129 $ hg push ../push-dest -f # force because we push multiple heads
129 $ hg push ../push-dest -f # force because we push multiple heads
130 pushing to ../push-dest
130 pushing to ../push-dest
131 searching for changes
131 searching for changes
132 adding changesets
132 adding changesets
133 adding manifests
133 adding manifests
134 adding file changes
134 adding file changes
135 added 5 changesets with 5 changes to 5 files (+1 heads)
135 added 5 changesets with 5 changes to 5 files (+1 heads)
136 $ hglog
136 $ hglog
137 7 2 merge B' and E
137 7 2 merge B' and E
138 6 1 B'
138 6 1 B'
139 5 2 H
139 5 2 H
140 4 2 E
140 4 2 E
141 3 1 D
141 3 1 D
142 2 1 C
142 2 1 C
143 1 0 B
143 1 0 B
144 0 0 A
144 0 0 A
145 $ cd ../push-dest
145 $ cd ../push-dest
146 $ hglog
146 $ hglog
147 4 1 B'
147 4 1 B'
148 3 1 D
148 3 1 D
149 2 1 C
149 2 1 C
150 1 0 B
150 1 0 B
151 0 0 A
151 0 0 A
152
152
153 (Issue3303)
153 (Issue3303)
154 Check that remote secret changeset are ignore when checking creation of remote heads
154 Check that remote secret changeset are ignore when checking creation of remote heads
155
155
156 We add a secret head into the push destination. This secret head shadows a
156 We add a secret head into the push destination. This secret head shadows a
157 visible shared between the initial repo and the push destination.
157 visible shared between the initial repo and the push destination.
158
158
159 $ hg up -q 4 # B'
159 $ hg up -q 4 # B'
160 $ mkcommit Z --config phases.new-commit=secret
160 $ mkcommit Z --config phases.new-commit=secret
161 $ hg phase .
161 $ hg phase .
162 5: secret
162 5: secret
163
163
164 We now try to push a new public changeset that descend from the common public
164 We now try to push a new public changeset that descend from the common public
165 head shadowed by the remote secret head.
165 head shadowed by the remote secret head.
166
166
167 $ cd ../initialrepo
167 $ cd ../initialrepo
168 $ hg up -q 6 #B'
168 $ hg up -q 6 #B'
169 $ mkcommit I
169 $ mkcommit I
170 created new head
170 created new head
171 $ hg push ../push-dest
171 $ hg push ../push-dest
172 pushing to ../push-dest
172 pushing to ../push-dest
173 searching for changes
173 searching for changes
174 adding changesets
174 adding changesets
175 adding manifests
175 adding manifests
176 adding file changes
176 adding file changes
177 added 1 changesets with 1 changes to 1 files (+1 heads)
177 added 1 changesets with 1 changes to 1 files (+1 heads)
178
178
179 :note: The "(+1 heads)" is wrong as we do not had any visible head
179 :note: The "(+1 heads)" is wrong as we do not had any visible head
180
180
181 check that branch cache with "served" filter are properly computed and stored
181 check that branch cache with "served" filter are properly computed and stored
182
182
183 $ ls ../push-dest/.hg/cache/branch2*
183 $ ls ../push-dest/.hg/cache/branch2*
184 ../push-dest/.hg/cache/branch2-served
184 ../push-dest/.hg/cache/branch2-served
185 $ cat ../push-dest/.hg/cache/branch2-served
185 $ cat ../push-dest/.hg/cache/branch2-served
186 6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
186 6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
187 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
187 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
188 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
188 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
189 $ hg heads -R ../push-dest --template '{rev}:{node} {phase}\n' #update visible cache too
189 $ hg heads -R ../push-dest --template '{rev}:{node} {phase}\n' #update visible cache too
190 6:6d6770faffce199f1fddd1cf87f6f026138cf061 draft
190 6:6d6770faffce199f1fddd1cf87f6f026138cf061 draft
191 5:2713879da13d6eea1ff22b442a5a87cb31a7ce6a secret
191 5:2713879da13d6eea1ff22b442a5a87cb31a7ce6a secret
192 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e draft
192 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e draft
193 $ ls ../push-dest/.hg/cache/branch2*
193 $ ls ../push-dest/.hg/cache/branch2*
194 ../push-dest/.hg/cache/branch2-served
194 ../push-dest/.hg/cache/branch2-served
195 ../push-dest/.hg/cache/branch2-visible
195 ../push-dest/.hg/cache/branch2-visible
196 $ cat ../push-dest/.hg/cache/branch2-served
196 $ cat ../push-dest/.hg/cache/branch2-served
197 6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
197 6d6770faffce199f1fddd1cf87f6f026138cf061 6 465891ffab3c47a3c23792f7dc84156e19a90722
198 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
198 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
199 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
199 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
200 $ cat ../push-dest/.hg/cache/branch2-visible
200 $ cat ../push-dest/.hg/cache/branch2-visible
201 6d6770faffce199f1fddd1cf87f6f026138cf061 6
201 6d6770faffce199f1fddd1cf87f6f026138cf061 6
202 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
202 b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e o default
203 2713879da13d6eea1ff22b442a5a87cb31a7ce6a o default
203 2713879da13d6eea1ff22b442a5a87cb31a7ce6a o default
204 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
204 6d6770faffce199f1fddd1cf87f6f026138cf061 o default
205
205
206
206
207 Restore condition prior extra insertion.
207 Restore condition prior extra insertion.
208 $ hg -q --config extensions.mq= strip .
208 $ hg -q --config extensions.mq= strip .
209 $ hg up -q 7
209 $ hg up -q 7
210 $ cd ..
210 $ cd ..
211
211
212 Test secret changeset are not pull
212 Test secret changeset are not pull
213
213
214 $ hg init pull-dest
214 $ hg init pull-dest
215 $ cd pull-dest
215 $ cd pull-dest
216 $ hg pull ../initialrepo
216 $ hg pull ../initialrepo
217 pulling from ../initialrepo
217 pulling from ../initialrepo
218 requesting all changes
218 requesting all changes
219 adding changesets
219 adding changesets
220 adding manifests
220 adding manifests
221 adding file changes
221 adding file changes
222 added 5 changesets with 5 changes to 5 files (+1 heads)
222 added 5 changesets with 5 changes to 5 files (+1 heads)
223 (run 'hg heads' to see heads, 'hg merge' to merge)
223 (run 'hg heads' to see heads, 'hg merge' to merge)
224 $ hglog
224 $ hglog
225 4 0 B'
225 4 0 B'
226 3 0 D
226 3 0 D
227 2 0 C
227 2 0 C
228 1 0 B
228 1 0 B
229 0 0 A
229 0 0 A
230 $ cd ..
230 $ cd ..
231
231
232 But secret can still be bundled explicitly
232 But secret can still be bundled explicitly
233
233
234 $ cd initialrepo
234 $ cd initialrepo
235 $ hg bundle --base '4^' -r 'children(4)' ../secret-bundle.hg
235 $ hg bundle --base '4^' -r 'children(4)' ../secret-bundle.hg
236 4 changesets found
236 4 changesets found
237 $ cd ..
237 $ cd ..
238
238
239 Test secret changeset are not cloned
239 Test secret changeset are not cloned
240 (during local clone)
240 (during local clone)
241
241
242 $ hg clone -qU initialrepo clone-dest
242 $ hg clone -qU initialrepo clone-dest
243 $ hglog -R clone-dest
243 $ hglog -R clone-dest
244 4 0 B'
244 4 0 B'
245 3 0 D
245 3 0 D
246 2 0 C
246 2 0 C
247 1 0 B
247 1 0 B
248 0 0 A
248 0 0 A
249
249
250 Test summary
250 Test summary
251
251
252 $ hg summary -R clone-dest --verbose
252 $ hg summary -R clone-dest --verbose
253 parent: -1:000000000000 (no revision checked out)
253 parent: -1:000000000000 (no revision checked out)
254 branch: default
254 branch: default
255 commit: (clean)
255 commit: (clean)
256 update: 5 new changesets (update)
256 update: 5 new changesets (update)
257 $ hg summary -R initialrepo
257 $ hg summary -R initialrepo
258 parent: 7:17a481b3bccb tip
258 parent: 7:17a481b3bccb tip
259 merge B' and E
259 merge B' and E
260 branch: default
260 branch: default
261 commit: (clean) (secret)
261 commit: (clean) (secret)
262 update: 1 new changesets, 2 branch heads (merge)
262 update: 1 new changesets, 2 branch heads (merge)
263 phases: 3 draft, 3 secret
263 phases: 3 draft, 3 secret
264 $ hg summary -R initialrepo --quiet
264 $ hg summary -R initialrepo --quiet
265 parent: 7:17a481b3bccb tip
265 parent: 7:17a481b3bccb tip
266 update: 1 new changesets, 2 branch heads (merge)
266 update: 1 new changesets, 2 branch heads (merge)
267
267
268 Test revset
268 Test revset
269
269
270 $ cd initialrepo
270 $ cd initialrepo
271 $ hglog -r 'public()'
271 $ hglog -r 'public()'
272 0 0 A
272 0 0 A
273 1 0 B
273 1 0 B
274 $ hglog -r 'draft()'
274 $ hglog -r 'draft()'
275 2 1 C
275 2 1 C
276 3 1 D
276 3 1 D
277 6 1 B'
277 6 1 B'
278 $ hglog -r 'secret()'
278 $ hglog -r 'secret()'
279 4 2 E
279 4 2 E
280 5 2 H
280 5 2 H
281 7 2 merge B' and E
281 7 2 merge B' and E
282
282
283 test that phase are displayed in log at debug level
283 test that phase are displayed in log at debug level
284
284
285 $ hg log --debug
285 $ hg log --debug
286 changeset: 7:17a481b3bccb796c0521ae97903d81c52bfee4af
286 changeset: 7:17a481b3bccb796c0521ae97903d81c52bfee4af
287 tag: tip
287 tag: tip
288 phase: secret
288 phase: secret
289 parent: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
289 parent: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
290 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
290 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
291 manifest: 7:5e724ffacba267b2ab726c91fc8b650710deaaa8
291 manifest: 7:5e724ffacba267b2ab726c91fc8b650710deaaa8
292 user: test
292 user: test
293 date: Thu Jan 01 00:00:00 1970 +0000
293 date: Thu Jan 01 00:00:00 1970 +0000
294 files+: C D E
294 files+: C D E
295 extra: branch=default
295 extra: branch=default
296 description:
296 description:
297 merge B' and E
297 merge B' and E
298
298
299
299
300 changeset: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
300 changeset: 6:cf9fe039dfd67e829edf6522a45de057b5c86519
301 phase: draft
301 phase: draft
302 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
302 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
303 parent: -1:0000000000000000000000000000000000000000
303 parent: -1:0000000000000000000000000000000000000000
304 manifest: 6:ab8bfef2392903058bf4ebb9e7746e8d7026b27a
304 manifest: 6:ab8bfef2392903058bf4ebb9e7746e8d7026b27a
305 user: test
305 user: test
306 date: Thu Jan 01 00:00:00 1970 +0000
306 date: Thu Jan 01 00:00:00 1970 +0000
307 files+: B'
307 files+: B'
308 extra: branch=default
308 extra: branch=default
309 description:
309 description:
310 B'
310 B'
311
311
312
312
313 changeset: 5:a030c6be5127abc010fcbff1851536552e6951a8
313 changeset: 5:a030c6be5127abc010fcbff1851536552e6951a8
314 phase: secret
314 phase: secret
315 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
315 parent: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
316 parent: -1:0000000000000000000000000000000000000000
316 parent: -1:0000000000000000000000000000000000000000
317 manifest: 5:5c710aa854874fe3d5fa7192e77bdb314cc08b5a
317 manifest: 5:5c710aa854874fe3d5fa7192e77bdb314cc08b5a
318 user: test
318 user: test
319 date: Thu Jan 01 00:00:00 1970 +0000
319 date: Thu Jan 01 00:00:00 1970 +0000
320 files+: H
320 files+: H
321 extra: branch=default
321 extra: branch=default
322 description:
322 description:
323 H
323 H
324
324
325
325
326 changeset: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
326 changeset: 4:a603bfb5a83e312131cebcd05353c217d4d21dde
327 phase: secret
327 phase: secret
328 parent: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
328 parent: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
329 parent: -1:0000000000000000000000000000000000000000
329 parent: -1:0000000000000000000000000000000000000000
330 manifest: 4:7173fd1c27119750b959e3a0f47ed78abe75d6dc
330 manifest: 4:7173fd1c27119750b959e3a0f47ed78abe75d6dc
331 user: test
331 user: test
332 date: Thu Jan 01 00:00:00 1970 +0000
332 date: Thu Jan 01 00:00:00 1970 +0000
333 files+: E
333 files+: E
334 extra: branch=default
334 extra: branch=default
335 description:
335 description:
336 E
336 E
337
337
338
338
339 changeset: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
339 changeset: 3:b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e
340 phase: draft
340 phase: draft
341 parent: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
341 parent: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
342 parent: -1:0000000000000000000000000000000000000000
342 parent: -1:0000000000000000000000000000000000000000
343 manifest: 3:6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c
343 manifest: 3:6e1f4c47ecb533ffd0c8e52cdc88afb6cd39e20c
344 user: test
344 user: test
345 date: Thu Jan 01 00:00:00 1970 +0000
345 date: Thu Jan 01 00:00:00 1970 +0000
346 files+: D
346 files+: D
347 extra: branch=default
347 extra: branch=default
348 description:
348 description:
349 D
349 D
350
350
351
351
352 changeset: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
352 changeset: 2:f838bfaca5c7226600ebcfd84f3c3c13a28d3757
353 phase: draft
353 phase: draft
354 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
354 parent: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
355 parent: -1:0000000000000000000000000000000000000000
355 parent: -1:0000000000000000000000000000000000000000
356 manifest: 2:66a5a01817fdf5239c273802b5b7618d051c89e4
356 manifest: 2:66a5a01817fdf5239c273802b5b7618d051c89e4
357 user: test
357 user: test
358 date: Thu Jan 01 00:00:00 1970 +0000
358 date: Thu Jan 01 00:00:00 1970 +0000
359 files+: C
359 files+: C
360 extra: branch=default
360 extra: branch=default
361 description:
361 description:
362 C
362 C
363
363
364
364
365 changeset: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
365 changeset: 1:27547f69f25460a52fff66ad004e58da7ad3fb56
366 phase: public
366 phase: public
367 parent: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
367 parent: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
368 parent: -1:0000000000000000000000000000000000000000
368 parent: -1:0000000000000000000000000000000000000000
369 manifest: 1:cb5cbbc1bfbf24cc34b9e8c16914e9caa2d2a7fd
369 manifest: 1:cb5cbbc1bfbf24cc34b9e8c16914e9caa2d2a7fd
370 user: test
370 user: test
371 date: Thu Jan 01 00:00:00 1970 +0000
371 date: Thu Jan 01 00:00:00 1970 +0000
372 files+: B
372 files+: B
373 extra: branch=default
373 extra: branch=default
374 description:
374 description:
375 B
375 B
376
376
377
377
378 changeset: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
378 changeset: 0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
379 phase: public
379 phase: public
380 parent: -1:0000000000000000000000000000000000000000
380 parent: -1:0000000000000000000000000000000000000000
381 parent: -1:0000000000000000000000000000000000000000
381 parent: -1:0000000000000000000000000000000000000000
382 manifest: 0:007d8c9d88841325f5c6b06371b35b4e8a2b1a83
382 manifest: 0:007d8c9d88841325f5c6b06371b35b4e8a2b1a83
383 user: test
383 user: test
384 date: Thu Jan 01 00:00:00 1970 +0000
384 date: Thu Jan 01 00:00:00 1970 +0000
385 files+: A
385 files+: A
386 extra: branch=default
386 extra: branch=default
387 description:
387 description:
388 A
388 A
389
389
390
390
391
391
392
392
393 (Issue3707)
393 (Issue3707)
394 test invalid phase name
394 test invalid phase name
395
395
396 $ mkcommit I --config phases.new-commit='babar'
396 $ mkcommit I --config phases.new-commit='babar'
397 transaction abort!
397 transaction abort!
398 rollback completed
398 rollback completed
399 abort: phases.new-commit: not a valid phase name ('babar')
399 abort: phases.new-commit: not a valid phase name ('babar')
400 [255]
400 [255]
401 Test phase command
401 Test phase command
402 ===================
402 ===================
403
403
404 initial picture
404 initial picture
405
405
406 $ hg log -G --template "{rev} {phase} {desc}\n"
406 $ hg log -G --template "{rev} {phase} {desc}\n"
407 @ 7 secret merge B' and E
407 @ 7 secret merge B' and E
408 |\
408 |\
409 | o 6 draft B'
409 | o 6 draft B'
410 | |
410 | |
411 +---o 5 secret H
411 +---o 5 secret H
412 | |
412 | |
413 o | 4 secret E
413 o | 4 secret E
414 | |
414 | |
415 o | 3 draft D
415 o | 3 draft D
416 | |
416 | |
417 o | 2 draft C
417 o | 2 draft C
418 |/
418 |/
419 o 1 public B
419 o 1 public B
420 |
420 |
421 o 0 public A
421 o 0 public A
422
422
423
423
424 display changesets phase
424 display changesets phase
425
425
426 (mixing -r and plain rev specification)
426 (mixing -r and plain rev specification)
427
427
428 $ hg phase 1::4 -r 7
428 $ hg phase 1::4 -r 7
429 1: public
429 1: public
430 2: draft
430 2: draft
431 3: draft
431 3: draft
432 4: secret
432 4: secret
433 7: secret
433 7: secret
434
434
435
435
436 move changeset forward
436 move changeset forward
437
437
438 (with -r option)
438 (with -r option)
439
439
440 $ hg phase --public -r 2
440 $ hg phase --public -r 2
441 $ hg log -G --template "{rev} {phase} {desc}\n"
441 $ hg log -G --template "{rev} {phase} {desc}\n"
442 @ 7 secret merge B' and E
442 @ 7 secret merge B' and E
443 |\
443 |\
444 | o 6 draft B'
444 | o 6 draft B'
445 | |
445 | |
446 +---o 5 secret H
446 +---o 5 secret H
447 | |
447 | |
448 o | 4 secret E
448 o | 4 secret E
449 | |
449 | |
450 o | 3 draft D
450 o | 3 draft D
451 | |
451 | |
452 o | 2 public C
452 o | 2 public C
453 |/
453 |/
454 o 1 public B
454 o 1 public B
455 |
455 |
456 o 0 public A
456 o 0 public A
457
457
458
458
459 move changeset backward
459 move changeset backward
460
460
461 (without -r option)
461 (without -r option)
462
462
463 $ hg phase --draft --force 2
463 $ hg phase --draft --force 2
464 $ hg log -G --template "{rev} {phase} {desc}\n"
464 $ hg log -G --template "{rev} {phase} {desc}\n"
465 @ 7 secret merge B' and E
465 @ 7 secret merge B' and E
466 |\
466 |\
467 | o 6 draft B'
467 | o 6 draft B'
468 | |
468 | |
469 +---o 5 secret H
469 +---o 5 secret H
470 | |
470 | |
471 o | 4 secret E
471 o | 4 secret E
472 | |
472 | |
473 o | 3 draft D
473 o | 3 draft D
474 | |
474 | |
475 o | 2 draft C
475 o | 2 draft C
476 |/
476 |/
477 o 1 public B
477 o 1 public B
478 |
478 |
479 o 0 public A
479 o 0 public A
480
480
481
481
482 move changeset forward and backward and test kill switch
482 move changeset forward and backward and test kill switch
483
483
484 $ cat <<EOF >> $HGRCPATH
484 $ cat <<EOF >> $HGRCPATH
485 > [experimental]
485 > [experimental]
486 > nativephaseskillswitch = true
486 > nativephaseskillswitch = true
487 > EOF
487 > EOF
488 $ hg phase --draft --force 1::4
488 $ hg phase --draft --force 1::4
489 $ hg log -G --template "{rev} {phase} {desc}\n"
489 $ hg log -G --template "{rev} {phase} {desc}\n"
490 @ 7 secret merge B' and E
490 @ 7 secret merge B' and E
491 |\
491 |\
492 | o 6 draft B'
492 | o 6 draft B'
493 | |
493 | |
494 +---o 5 secret H
494 +---o 5 secret H
495 | |
495 | |
496 o | 4 draft E
496 o | 4 draft E
497 | |
497 | |
498 o | 3 draft D
498 o | 3 draft D
499 | |
499 | |
500 o | 2 draft C
500 o | 2 draft C
501 |/
501 |/
502 o 1 draft B
502 o 1 draft B
503 |
503 |
504 o 0 public A
504 o 0 public A
505
505
506 test partial failure
506 test partial failure
507
507
508 $ cat <<EOF >> $HGRCPATH
508 $ cat <<EOF >> $HGRCPATH
509 > [experimental]
509 > [experimental]
510 > nativephaseskillswitch = false
510 > nativephaseskillswitch = false
511 > EOF
511 > EOF
512 $ hg phase --public 7
512 $ hg phase --public 7
513 $ hg phase --draft '5 or 7'
513 $ hg phase --draft '5 or 7'
514 cannot move 1 changesets to a higher phase, use --force
514 cannot move 1 changesets to a higher phase, use --force
515 phase changed for 1 changesets
515 phase changed for 1 changesets
516 [1]
516 [1]
517 $ hg log -G --template "{rev} {phase} {desc}\n"
517 $ hg log -G --template "{rev} {phase} {desc}\n"
518 @ 7 public merge B' and E
518 @ 7 public merge B' and E
519 |\
519 |\
520 | o 6 public B'
520 | o 6 public B'
521 | |
521 | |
522 +---o 5 draft H
522 +---o 5 draft H
523 | |
523 | |
524 o | 4 public E
524 o | 4 public E
525 | |
525 | |
526 o | 3 public D
526 o | 3 public D
527 | |
527 | |
528 o | 2 public C
528 o | 2 public C
529 |/
529 |/
530 o 1 public B
530 o 1 public B
531 |
531 |
532 o 0 public A
532 o 0 public A
533
533
534
534
535 test complete failure
535 test complete failure
536
536
537 $ hg phase --draft 7
537 $ hg phase --draft 7
538 cannot move 1 changesets to a higher phase, use --force
538 cannot move 1 changesets to a higher phase, use --force
539 no phases changed
539 no phases changed
540 [1]
540 [1]
541
541
542 $ cd ..
542 $ cd ..
543
543
544 test hidden changeset are not cloned as public (issue3935)
544 test hidden changeset are not cloned as public (issue3935)
545
545
546 $ cd initialrepo
546 $ cd initialrepo
547
547
548 (enabling evolution)
548 (enabling evolution)
549 $ cat >> $HGRCPATH << EOF
549 $ cat >> $HGRCPATH << EOF
550 > [experimental]
550 > [experimental]
551 > evolution=createmarkers
551 > evolution=createmarkers
552 > EOF
552 > EOF
553
553
554 (making a changeset hidden; H in that case)
554 (making a changeset hidden; H in that case)
555 $ hg debugobsolete `hg id --debug -r 5`
555 $ hg debugobsolete `hg id --debug -r 5`
556
556
557 $ cd ..
557 $ cd ..
558 $ hg clone initialrepo clonewithobs
558 $ hg clone initialrepo clonewithobs
559 requesting all changes
559 requesting all changes
560 adding changesets
560 adding changesets
561 adding manifests
561 adding manifests
562 adding file changes
562 adding file changes
563 added 7 changesets with 6 changes to 6 files
563 added 7 changesets with 6 changes to 6 files
564 updating to branch default
564 updating to branch default
565 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
565 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
566 $ cd clonewithobs
566 $ cd clonewithobs
567 $ hg log -G --template "{rev} {phase} {desc}\n"
567 $ hg log -G --template "{rev} {phase} {desc}\n"
568 @ 6 public merge B' and E
568 @ 6 public merge B' and E
569 |\
569 |\
570 | o 5 public B'
570 | o 5 public B'
571 | |
571 | |
572 o | 4 public E
572 o | 4 public E
573 | |
573 | |
574 o | 3 public D
574 o | 3 public D
575 | |
575 | |
576 o | 2 public C
576 o | 2 public C
577 |/
577 |/
578 o 1 public B
578 o 1 public B
579 |
579 |
580 o 0 public A
580 o 0 public A
581
581
582
582
583 test verify repo containing hidden changesets, which should not abort just
583 test verify repo containing hidden changesets, which should not abort just
584 because repo.cancopy() is False
584 because repo.cancopy() is False
585
585
586 $ cd ../initialrepo
586 $ cd ../initialrepo
587 $ hg verify
587 $ hg verify
588 checking changesets
588 checking changesets
589 checking manifests
589 checking manifests
590 crosschecking files in changesets and manifests
590 crosschecking files in changesets and manifests
591 checking files
591 checking files
592 7 files, 8 changesets, 7 total revisions
592 7 files, 8 changesets, 7 total revisions
593
594 $ cd ..
595
596 check whether HG_PENDING makes pending changes only in related
597 repositories visible to an external hook.
598
599 (emulate a transaction running concurrently by copied
600 .hg/phaseroots.pending in subsequent test)
601
602 $ cat > $TESTTMP/savepending.sh <<EOF
603 > cp .hg/store/phaseroots.pending .hg/store/phaseroots.pending.saved
604 > exit 1 # to avoid changing phase for subsequent tests
605 > EOF
606 $ cd push-dest
607 $ hg phase 6
608 6: draft
609 $ hg --config hooks.pretxnclose="sh $TESTTMP/savepending.sh" phase -f -s 6
610 transaction abort!
611 rollback completed
612 abort: pretxnclose hook exited with status 1
613 [255]
614 $ cp .hg/store/phaseroots.pending.saved .hg/store/phaseroots.pending
615
616 (check (in)visibility of phaseroot while transaction running in repo)
617
618 $ cat > $TESTTMP/checkpending.sh <<EOF
619 > echo '@initialrepo'
620 > hg -R $TESTTMP/initialrepo phase 7
621 > echo '@push-dest'
622 > hg -R $TESTTMP/push-dest phase 6
623 > exit 1 # to avoid changing phase for subsequent tests
624 > EOF
625 $ cd ../initialrepo
626 $ hg phase 7
627 7: public
628 $ hg --config hooks.pretxnclose="sh $TESTTMP/checkpending.sh" phase -f -s 7
629 @initialrepo
630 7: secret
631 @push-dest
632 6: draft
633 transaction abort!
634 rollback completed
635 abort: pretxnclose hook exited with status 1
636 [255]
General Comments 0
You need to be logged in to leave comments. Login now