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