##// END OF EJS Templates
obsolete: add a flag that allows fixing "bumped" changeset...
Pierre-Yves David -
r17831:70b08df2 default
parent child Browse files
Show More
@@ -1,491 +1,526 b''
1 1 # obsolete.py - obsolete markers handling
2 2 #
3 3 # Copyright 2012 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
4 4 # Logilab SA <contact@logilab.fr>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 """Obsolete markers handling
10 10
11 11 An obsolete marker maps an old changeset to a list of new
12 12 changesets. If the list of new changesets is empty, the old changeset
13 13 is said to be "killed". Otherwise, the old changeset is being
14 14 "replaced" by the new changesets.
15 15
16 16 Obsolete markers can be used to record and distribute changeset graph
17 17 transformations performed by history rewriting operations, and help
18 18 building new tools to reconciliate conflicting rewriting actions. To
19 19 facilitate conflicts resolution, markers include various annotations
20 20 besides old and news changeset identifiers, such as creation date or
21 21 author name.
22 22
23 23 The old obsoleted changeset is called "precursor" and possible replacements are
24 24 called "successors". Markers that used changeset X as a precursors are called
25 25 "successor markers of X" because they hold information about the successors of
26 26 X. Markers that use changeset Y as a successors are call "precursor markers of
27 27 Y" because they hold information about the precursors of Y.
28 28
29 29 Examples:
30 30
31 31 - When changeset A is replacement by a changeset A', one marker is stored:
32 32
33 33 (A, (A'))
34 34
35 35 - When changesets A and B are folded into a new changeset C two markers are
36 36 stored:
37 37
38 38 (A, (C,)) and (B, (C,))
39 39
40 40 - When changeset A is simply "pruned" from the graph, a marker in create:
41 41
42 42 (A, ())
43 43
44 44 - When changeset A is split into B and C, a single marker are used:
45 45
46 46 (A, (C, C))
47 47
48 48 We use a single marker to distinct the "split" case from the "divergence"
49 49 case. If two independants operation rewrite the same changeset A in to A' and
50 50 A'' when have an error case: divergent rewriting. We can detect it because
51 51 two markers will be created independently:
52 52
53 53 (A, (B,)) and (A, (C,))
54 54
55 55 Format
56 56 ------
57 57
58 58 Markers are stored in an append-only file stored in
59 59 '.hg/store/obsstore'.
60 60
61 61 The file starts with a version header:
62 62
63 63 - 1 unsigned byte: version number, starting at zero.
64 64
65 65
66 66 The header is followed by the markers. Each marker is made of:
67 67
68 68 - 1 unsigned byte: number of new changesets "R", could be zero.
69 69
70 70 - 1 unsigned 32-bits integer: metadata size "M" in bytes.
71 71
72 72 - 1 byte: a bit field. It is reserved for flags used in obsolete
73 73 markers common operations, to avoid repeated decoding of metadata
74 74 entries.
75 75
76 76 - 20 bytes: obsoleted changeset identifier.
77 77
78 78 - N*20 bytes: new changesets identifiers.
79 79
80 80 - M bytes: metadata as a sequence of nul-terminated strings. Each
81 81 string contains a key and a value, separated by a color ':', without
82 82 additional encoding. Keys cannot contain '\0' or ':' and values
83 83 cannot contain '\0'.
84 84 """
85 85 import struct
86 86 import util, base85, node
87 87 from i18n import _
88 88
89 89 _pack = struct.pack
90 90 _unpack = struct.unpack
91 91
92 92 _SEEK_END = 2 # os.SEEK_END was introduced in Python 2.5
93 93
94 94 # the obsolete feature is not mature enough to be enabled by default.
95 95 # you have to rely on third party extension extension to enable this.
96 96 _enabled = False
97 97
98 98 # data used for parsing and writing
99 99 _fmversion = 0
100 100 _fmfixed = '>BIB20s'
101 101 _fmnode = '20s'
102 102 _fmfsize = struct.calcsize(_fmfixed)
103 103 _fnodesize = struct.calcsize(_fmnode)
104 104
105 ### obsolescence marker flag
106
107 ## bumpedfix flag
108 #
109 # When a changeset A' succeed to a changeset A which became public, we call A'
110 # "bumped" because it's a successors of a public changesets
111 #
112 # o A' (bumped)
113 # |`:
114 # | o A
115 # |/
116 # o Z
117 #
118 # The way to solve this situation is to create a new changeset Ad as children
119 # of A. This changeset have the same content than A'. So the diff from A to A'
120 # is the same than the diff from A to Ad. Ad is marked as a successors of A'
121 #
122 # o Ad
123 # |`:
124 # | x A'
125 # |'|
126 # o | A
127 # |/
128 # o Z
129 #
130 # But by transitivity Ad is also a successors of A. To avoid having Ad marked
131 # as bumped too, we add the `bumpedfix` flag to the marker. <A', (Ad,)>.
132 # This flag mean that the successors are an interdiff that fix the bumped
133 # situation, breaking the transitivity of "bumped" here.
134 bumpedfix = 1
135
105 136 def _readmarkers(data):
106 137 """Read and enumerate markers from raw data"""
107 138 off = 0
108 139 diskversion = _unpack('>B', data[off:off + 1])[0]
109 140 off += 1
110 141 if diskversion != _fmversion:
111 142 raise util.Abort(_('parsing obsolete marker: unknown version %r')
112 143 % diskversion)
113 144
114 145 # Loop on markers
115 146 l = len(data)
116 147 while off + _fmfsize <= l:
117 148 # read fixed part
118 149 cur = data[off:off + _fmfsize]
119 150 off += _fmfsize
120 151 nbsuc, mdsize, flags, pre = _unpack(_fmfixed, cur)
121 152 # read replacement
122 153 sucs = ()
123 154 if nbsuc:
124 155 s = (_fnodesize * nbsuc)
125 156 cur = data[off:off + s]
126 157 sucs = _unpack(_fmnode * nbsuc, cur)
127 158 off += s
128 159 # read metadata
129 160 # (metadata will be decoded on demand)
130 161 metadata = data[off:off + mdsize]
131 162 if len(metadata) != mdsize:
132 163 raise util.Abort(_('parsing obsolete marker: metadata is too '
133 164 'short, %d bytes expected, got %d')
134 165 % (mdsize, len(metadata)))
135 166 off += mdsize
136 167 yield (pre, sucs, flags, metadata)
137 168
138 169 def encodemeta(meta):
139 170 """Return encoded metadata string to string mapping.
140 171
141 172 Assume no ':' in key and no '\0' in both key and value."""
142 173 for key, value in meta.iteritems():
143 174 if ':' in key or '\0' in key:
144 175 raise ValueError("':' and '\0' are forbidden in metadata key'")
145 176 if '\0' in value:
146 177 raise ValueError("':' are forbidden in metadata value'")
147 178 return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
148 179
149 180 def decodemeta(data):
150 181 """Return string to string dictionary from encoded version."""
151 182 d = {}
152 183 for l in data.split('\0'):
153 184 if l:
154 185 key, value = l.split(':')
155 186 d[key] = value
156 187 return d
157 188
158 189 class marker(object):
159 190 """Wrap obsolete marker raw data"""
160 191
161 192 def __init__(self, repo, data):
162 193 # the repo argument will be used to create changectx in later version
163 194 self._repo = repo
164 195 self._data = data
165 196 self._decodedmeta = None
166 197
167 198 def precnode(self):
168 199 """Precursor changeset node identifier"""
169 200 return self._data[0]
170 201
171 202 def succnodes(self):
172 203 """List of successor changesets node identifiers"""
173 204 return self._data[1]
174 205
175 206 def metadata(self):
176 207 """Decoded metadata dictionary"""
177 208 if self._decodedmeta is None:
178 209 self._decodedmeta = decodemeta(self._data[3])
179 210 return self._decodedmeta
180 211
181 212 def date(self):
182 213 """Creation date as (unixtime, offset)"""
183 214 parts = self.metadata()['date'].split(' ')
184 215 return (float(parts[0]), int(parts[1]))
185 216
186 217 class obsstore(object):
187 218 """Store obsolete markers
188 219
189 220 Markers can be accessed with two mappings:
190 221 - precursors[x] -> set(markers on precursors edges of x)
191 222 - successors[x] -> set(markers on successors edges of x)
192 223 """
193 224
194 225 def __init__(self, sopener):
195 226 # caches for various obsolescence related cache
196 227 self.caches = {}
197 228 self._all = []
198 229 # new markers to serialize
199 230 self.precursors = {}
200 231 self.successors = {}
201 232 self.sopener = sopener
202 233 data = sopener.tryread('obsstore')
203 234 if data:
204 235 self._load(_readmarkers(data))
205 236
206 237 def __iter__(self):
207 238 return iter(self._all)
208 239
209 240 def __nonzero__(self):
210 241 return bool(self._all)
211 242
212 243 def create(self, transaction, prec, succs=(), flag=0, metadata=None):
213 244 """obsolete: add a new obsolete marker
214 245
215 246 * ensuring it is hashable
216 247 * check mandatory metadata
217 248 * encode metadata
218 249 """
219 250 if metadata is None:
220 251 metadata = {}
221 252 if len(prec) != 20:
222 253 raise ValueError(prec)
223 254 for succ in succs:
224 255 if len(succ) != 20:
225 256 raise ValueError(succ)
226 257 marker = (str(prec), tuple(succs), int(flag), encodemeta(metadata))
227 258 self.add(transaction, [marker])
228 259
229 260 def add(self, transaction, markers):
230 261 """Add new markers to the store
231 262
232 263 Take care of filtering duplicate.
233 264 Return the number of new marker."""
234 265 if not _enabled:
235 266 raise util.Abort('obsolete feature is not enabled on this repo')
236 267 new = [m for m in markers if m not in self._all]
237 268 if new:
238 269 f = self.sopener('obsstore', 'ab')
239 270 try:
240 271 # Whether the file's current position is at the begin or at
241 272 # the end after opening a file for appending is implementation
242 273 # defined. So we must seek to the end before calling tell(),
243 274 # or we may get a zero offset for non-zero sized files on
244 275 # some platforms (issue3543).
245 276 f.seek(0, _SEEK_END)
246 277 offset = f.tell()
247 278 transaction.add('obsstore', offset)
248 279 # offset == 0: new file - add the version header
249 280 for bytes in _encodemarkers(new, offset == 0):
250 281 f.write(bytes)
251 282 finally:
252 283 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
253 284 # call 'filecacheentry.refresh()' here
254 285 f.close()
255 286 self._load(new)
256 287 # new marker *may* have changed several set. invalidate the cache.
257 288 self.caches.clear()
258 289 return len(new)
259 290
260 291 def mergemarkers(self, transaction, data):
261 292 markers = _readmarkers(data)
262 293 self.add(transaction, markers)
263 294
264 295 def _load(self, markers):
265 296 for mark in markers:
266 297 self._all.append(mark)
267 298 pre, sucs = mark[:2]
268 299 self.successors.setdefault(pre, set()).add(mark)
269 300 for suc in sucs:
270 301 self.precursors.setdefault(suc, set()).add(mark)
271 302 if node.nullid in self.precursors:
272 303 raise util.Abort(_('bad obsolescence marker detected: '
273 304 'invalid successors nullid'))
274 305
275 306 def _encodemarkers(markers, addheader=False):
276 307 # Kept separate from flushmarkers(), it will be reused for
277 308 # markers exchange.
278 309 if addheader:
279 310 yield _pack('>B', _fmversion)
280 311 for marker in markers:
281 312 yield _encodeonemarker(marker)
282 313
283 314
284 315 def _encodeonemarker(marker):
285 316 pre, sucs, flags, metadata = marker
286 317 nbsuc = len(sucs)
287 318 format = _fmfixed + (_fmnode * nbsuc)
288 319 data = [nbsuc, len(metadata), flags, pre]
289 320 data.extend(sucs)
290 321 return _pack(format, *data) + metadata
291 322
292 323 # arbitrary picked to fit into 8K limit from HTTP server
293 324 # you have to take in account:
294 325 # - the version header
295 326 # - the base85 encoding
296 327 _maxpayload = 5300
297 328
298 329 def listmarkers(repo):
299 330 """List markers over pushkey"""
300 331 if not repo.obsstore:
301 332 return {}
302 333 keys = {}
303 334 parts = []
304 335 currentlen = _maxpayload * 2 # ensure we create a new part
305 336 for marker in repo.obsstore:
306 337 nextdata = _encodeonemarker(marker)
307 338 if (len(nextdata) + currentlen > _maxpayload):
308 339 currentpart = []
309 340 currentlen = 0
310 341 parts.append(currentpart)
311 342 currentpart.append(nextdata)
312 343 currentlen += len(nextdata)
313 344 for idx, part in enumerate(reversed(parts)):
314 345 data = ''.join([_pack('>B', _fmversion)] + part)
315 346 keys['dump%i' % idx] = base85.b85encode(data)
316 347 return keys
317 348
318 349 def pushmarker(repo, key, old, new):
319 350 """Push markers over pushkey"""
320 351 if not key.startswith('dump'):
321 352 repo.ui.warn(_('unknown key: %r') % key)
322 353 return 0
323 354 if old:
324 355 repo.ui.warn(_('unexpected old value') % key)
325 356 return 0
326 357 data = base85.b85decode(new)
327 358 lock = repo.lock()
328 359 try:
329 360 tr = repo.transaction('pushkey: obsolete markers')
330 361 try:
331 362 repo.obsstore.mergemarkers(tr, data)
332 363 tr.close()
333 364 return 1
334 365 finally:
335 366 tr.release()
336 367 finally:
337 368 lock.release()
338 369
339 370 def allmarkers(repo):
340 371 """all obsolete markers known in a repository"""
341 372 for markerdata in repo.obsstore:
342 373 yield marker(repo, markerdata)
343 374
344 375 def precursormarkers(ctx):
345 376 """obsolete marker marking this changeset as a successors"""
346 377 for data in ctx._repo.obsstore.precursors.get(ctx.node(), ()):
347 378 yield marker(ctx._repo, data)
348 379
349 380 def successormarkers(ctx):
350 381 """obsolete marker making this changeset obsolete"""
351 382 for data in ctx._repo.obsstore.successors.get(ctx.node(), ()):
352 383 yield marker(ctx._repo, data)
353 384
354 def allsuccessors(obsstore, nodes):
385 def allsuccessors(obsstore, nodes, ignoreflags=0):
355 386 """Yield node for every successor of <nodes>.
356 387
357 388 Some successors may be unknown locally.
358 389
359 390 This is a linear yield unsuited to detecting split changesets."""
360 391 remaining = set(nodes)
361 392 seen = set(remaining)
362 393 while remaining:
363 394 current = remaining.pop()
364 395 yield current
365 396 for mark in obsstore.successors.get(current, ()):
397 # ignore marker flagged with with specified flag
398 if mark[2] & ignoreflags:
399 continue
366 400 for suc in mark[1]:
367 401 if suc not in seen:
368 402 seen.add(suc)
369 403 remaining.add(suc)
370 404
371 405 def _knownrevs(repo, nodes):
372 406 """yield revision numbers of known nodes passed in parameters
373 407
374 408 Unknown revisions are silently ignored."""
375 409 torev = repo.changelog.nodemap.get
376 410 for n in nodes:
377 411 rev = torev(n)
378 412 if rev is not None:
379 413 yield rev
380 414
381 415 # mapping of 'set-name' -> <function to compute this set>
382 416 cachefuncs = {}
383 417 def cachefor(name):
384 418 """Decorator to register a function as computing the cache for a set"""
385 419 def decorator(func):
386 420 assert name not in cachefuncs
387 421 cachefuncs[name] = func
388 422 return func
389 423 return decorator
390 424
391 425 def getrevs(repo, name):
392 426 """Return the set of revision that belong to the <name> set
393 427
394 428 Such access may compute the set and cache it for future use"""
395 429 if not repo.obsstore:
396 430 return ()
397 431 if name not in repo.obsstore.caches:
398 432 repo.obsstore.caches[name] = cachefuncs[name](repo)
399 433 return repo.obsstore.caches[name]
400 434
401 435 # To be simple we need to invalidate obsolescence cache when:
402 436 #
403 437 # - new changeset is added:
404 438 # - public phase is changed
405 439 # - obsolescence marker are added
406 440 # - strip is used a repo
407 441 def clearobscaches(repo):
408 442 """Remove all obsolescence related cache from a repo
409 443
410 444 This remove all cache in obsstore is the obsstore already exist on the
411 445 repo.
412 446
413 447 (We could be smarter here given the exact event that trigger the cache
414 448 clearing)"""
415 449 # only clear cache is there is obsstore data in this repo
416 450 if 'obsstore' in repo._filecache:
417 451 repo.obsstore.caches.clear()
418 452
419 453 @cachefor('obsolete')
420 454 def _computeobsoleteset(repo):
421 455 """the set of obsolete revisions"""
422 456 obs = set()
423 457 nm = repo.changelog.nodemap
424 458 for node in repo.obsstore.successors:
425 459 rev = nm.get(node)
426 460 if rev is not None:
427 461 obs.add(rev)
428 462 return set(repo.revs('%ld - public()', obs))
429 463
430 464 @cachefor('unstable')
431 465 def _computeunstableset(repo):
432 466 """the set of non obsolete revisions with obsolete parents"""
433 467 return set(repo.revs('(obsolete()::) - obsolete()'))
434 468
435 469 @cachefor('suspended')
436 470 def _computesuspendedset(repo):
437 471 """the set of obsolete parents with non obsolete descendants"""
438 472 return set(repo.revs('obsolete() and obsolete()::unstable()'))
439 473
440 474 @cachefor('extinct')
441 475 def _computeextinctset(repo):
442 476 """the set of obsolete parents without non obsolete descendants"""
443 477 return set(repo.revs('obsolete() - obsolete()::unstable()'))
444 478
445 479
446 480 @cachefor('bumped')
447 481 def _computebumpedset(repo):
448 482 """the set of revs trying to obsolete public revisions"""
449 483 # get all possible bumped changesets
450 484 tonode = repo.changelog.node
451 485 publicnodes = (tonode(r) for r in repo.revs('public()'))
452 successors = allsuccessors(repo.obsstore, publicnodes)
486 successors = allsuccessors(repo.obsstore, publicnodes,
487 ignoreflags=bumpedfix)
453 488 # revision public or already obsolete don't count as bumped
454 489 query = '%ld - obsolete() - public()'
455 490 return set(repo.revs(query, _knownrevs(repo, successors)))
456 491
457 492 def createmarkers(repo, relations, flag=0, metadata=None):
458 493 """Add obsolete markers between changesets in a repo
459 494
460 495 <relations> must be an iterable of (<old>, (<new>, ...)) tuple.
461 496 `old` and `news` are changectx.
462 497
463 498 Trying to obsolete a public changeset will raise an exception.
464 499
465 500 Current user and date are used except if specified otherwise in the
466 501 metadata attribute.
467 502
468 503 This function operates within a transaction of its own, but does
469 504 not take any lock on the repo.
470 505 """
471 506 # prepare metadata
472 507 if metadata is None:
473 508 metadata = {}
474 509 if 'date' not in metadata:
475 510 metadata['date'] = '%i %i' % util.makedate()
476 511 if 'user' not in metadata:
477 512 metadata['user'] = repo.ui.username()
478 513 tr = repo.transaction('add-obsolescence-marker')
479 514 try:
480 515 for prec, sucs in relations:
481 516 if not prec.mutable():
482 517 raise util.Abort("cannot obsolete immutable changeset: %s"
483 518 % prec)
484 519 nprec = prec.node()
485 520 nsucs = tuple(s.node() for s in sucs)
486 521 if nprec in nsucs:
487 522 raise util.Abort("changeset %s cannot obsolete itself" % prec)
488 523 repo.obsstore.create(tr, nprec, nsucs, flag, metadata)
489 524 tr.close()
490 525 finally:
491 526 tr.release()
@@ -1,534 +1,584 b''
1 1 $ cat >> $HGRCPATH << EOF
2 2 > [extensions]
3 3 > graphlog=
4 4 > [phases]
5 5 > # public changeset are not obsolete
6 6 > publish=false
7 7 > EOF
8 8 $ mkcommit() {
9 9 > echo "$1" > "$1"
10 10 > hg add "$1"
11 11 > hg ci -m "add $1"
12 12 > }
13 13 $ getid() {
14 14 > hg id --debug -ir "desc('$1')"
15 15 > }
16 16
17 17 $ cat > debugkeys.py <<EOF
18 18 > def reposetup(ui, repo):
19 19 > class debugkeysrepo(repo.__class__):
20 20 > def listkeys(self, namespace):
21 21 > ui.write('listkeys %s\n' % (namespace,))
22 22 > return super(debugkeysrepo, self).listkeys(namespace)
23 23 >
24 24 > if repo.local():
25 25 > repo.__class__ = debugkeysrepo
26 26 > EOF
27 27
28 28 $ hg init tmpa
29 29 $ cd tmpa
30 30 $ mkcommit kill_me
31 31
32 32 Checking that the feature is properly disabled
33 33
34 34 $ hg debugobsolete -d '0 0' `getid kill_me` -u babar
35 35 abort: obsolete feature is not enabled on this repo
36 36 [255]
37 37
38 38 Enabling it
39 39
40 40 $ cat > ../obs.py << EOF
41 41 > import mercurial.obsolete
42 42 > mercurial.obsolete._enabled = True
43 43 > EOF
44 44 $ echo '[extensions]' >> $HGRCPATH
45 45 $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
46 46
47 47 Killing a single changeset without replacement
48 48
49 49 $ hg debugobsolete 0
50 50 abort: changeset references must be full hexadecimal node identifiers
51 51 [255]
52 52 $ hg debugobsolete '00'
53 53 abort: changeset references must be full hexadecimal node identifiers
54 54 [255]
55 55 $ hg debugobsolete -d '0 0' `getid kill_me` -u babar
56 56 $ hg debugobsolete
57 57 97b7c2d76b1845ed3eb988cd612611e72406cef0 0 {'date': '0 0', 'user': 'babar'}
58 58 $ cd ..
59 59
60 60 Killing a single changeset with replacement
61 61
62 62 $ hg init tmpb
63 63 $ cd tmpb
64 64 $ mkcommit a
65 65 $ mkcommit b
66 66 $ mkcommit original_c
67 67 $ hg up "desc('b')"
68 68 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
69 69 $ mkcommit new_c
70 70 created new head
71 71 $ hg log -r 'hidden()' --template '{rev}:{node|short} {desc}\n' --hidden
72 72 $ hg debugobsolete --flag 12 `getid original_c` `getid new_c` -d '56 12'
73 73 $ hg log -r 'hidden()' --template '{rev}:{node|short} {desc}\n' --hidden
74 74 2:245bde4270cd add original_c
75 75 $ hg debugobsolete
76 76 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
77 77
78 78 do it again (it read the obsstore before adding new changeset)
79 79
80 80 $ hg up '.^'
81 81 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
82 82 $ mkcommit new_2_c
83 83 created new head
84 84 $ hg debugobsolete -d '1337 0' `getid new_c` `getid new_2_c`
85 85 $ hg debugobsolete
86 86 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
87 87 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
88 88
89 89 Register two markers with a missing node
90 90
91 91 $ hg up '.^'
92 92 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
93 93 $ mkcommit new_3_c
94 94 created new head
95 95 $ hg debugobsolete -d '1338 0' `getid new_2_c` 1337133713371337133713371337133713371337
96 96 $ hg debugobsolete -d '1339 0' 1337133713371337133713371337133713371337 `getid new_3_c`
97 97 $ hg debugobsolete
98 98 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
99 99 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
100 100 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
101 101 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
102 102
103 103 Refuse pathological nullid successors
104 104 $ hg debugobsolete -d '9001 0' 1337133713371337133713371337133713371337 0000000000000000000000000000000000000000
105 105 transaction abort!
106 106 rollback completed
107 107 abort: bad obsolescence marker detected: invalid successors nullid
108 108 [255]
109 109
110 110 Check that graphlog detect that a changeset is obsolete:
111 111
112 112 $ hg glog
113 113 @ changeset: 5:5601fb93a350
114 114 | tag: tip
115 115 | parent: 1:7c3bad9141dc
116 116 | user: test
117 117 | date: Thu Jan 01 00:00:00 1970 +0000
118 118 | summary: add new_3_c
119 119 |
120 120 o changeset: 1:7c3bad9141dc
121 121 | user: test
122 122 | date: Thu Jan 01 00:00:00 1970 +0000
123 123 | summary: add b
124 124 |
125 125 o changeset: 0:1f0dee641bb7
126 126 user: test
127 127 date: Thu Jan 01 00:00:00 1970 +0000
128 128 summary: add a
129 129
130 130
131 131 Check that public changeset are not accounted as obsolete:
132 132
133 133 $ hg phase --public 2
134 134 $ hg --config 'extensions.graphlog=' glog
135 135 @ changeset: 5:5601fb93a350
136 136 | tag: tip
137 137 | parent: 1:7c3bad9141dc
138 138 | user: test
139 139 | date: Thu Jan 01 00:00:00 1970 +0000
140 140 | summary: add new_3_c
141 141 |
142 142 | o changeset: 2:245bde4270cd
143 143 |/ user: test
144 144 | date: Thu Jan 01 00:00:00 1970 +0000
145 145 | summary: add original_c
146 146 |
147 147 o changeset: 1:7c3bad9141dc
148 148 | user: test
149 149 | date: Thu Jan 01 00:00:00 1970 +0000
150 150 | summary: add b
151 151 |
152 152 o changeset: 0:1f0dee641bb7
153 153 user: test
154 154 date: Thu Jan 01 00:00:00 1970 +0000
155 155 summary: add a
156 156
157 157
158 158 And that bumped changeset are detected
159 159 --------------------------------------
160 160
161 161 If we didn't filtered obsolete changesets out, 3 and 4 would show up too. Also
162 162 note that the bumped changeset (5:5601fb93a350) is not a direct successor of
163 163 the public changeset
164 164
165 165 $ hg log --hidden -r 'bumped()'
166 166 changeset: 5:5601fb93a350
167 167 tag: tip
168 168 parent: 1:7c3bad9141dc
169 169 user: test
170 170 date: Thu Jan 01 00:00:00 1970 +0000
171 171 summary: add new_3_c
172 172
173 173
174 Fixing "bumped" situation
175 We need to create a clone of 5 and add a special marker with a flag
176
177 $ hg up '5^'
178 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
179 $ hg revert -ar 5
180 adding new_3_c
181 $ hg ci -m 'add n3w_3_c'
182 created new head
183 $ hg debugobsolete -d '1338 0' --flags 1 `getid new_3_c` `getid n3w_3_c`
184 $ hg log -r 'bumped()'
185 $ hg log -G
186 @ changeset: 6:6f9641995072
187 | tag: tip
188 | parent: 1:7c3bad9141dc
189 | user: test
190 | date: Thu Jan 01 00:00:00 1970 +0000
191 | summary: add n3w_3_c
192 |
193 | o changeset: 2:245bde4270cd
194 |/ user: test
195 | date: Thu Jan 01 00:00:00 1970 +0000
196 | summary: add original_c
197 |
198 o changeset: 1:7c3bad9141dc
199 | user: test
200 | date: Thu Jan 01 00:00:00 1970 +0000
201 | summary: add b
202 |
203 o changeset: 0:1f0dee641bb7
204 user: test
205 date: Thu Jan 01 00:00:00 1970 +0000
206 summary: add a
207
208
209
210
174 211 $ cd ..
175 212
176 213 Exchange Test
177 214 ============================
178 215
179 216 Destination repo does not have any data
180 217 ---------------------------------------
181 218
182 219 Try to pull markers
183 220 (extinct changeset are excluded but marker are pushed)
184 221
185 222 $ hg init tmpc
186 223 $ cd tmpc
187 224 $ hg pull ../tmpb
188 225 pulling from ../tmpb
189 226 requesting all changes
190 227 adding changesets
191 228 adding manifests
192 229 adding file changes
193 230 added 4 changesets with 4 changes to 4 files (+1 heads)
194 231 (run 'hg heads' to see heads, 'hg merge' to merge)
195 232 $ hg debugobsolete
196 233 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
197 234 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
198 235 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
199 236 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
237 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 {'date': '1338 0', 'user': 'test'}
200 238
201 239 Rollback//Transaction support
202 240
203 241 $ hg debugobsolete -d '1340 0' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
204 242 $ hg debugobsolete
205 243 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
206 244 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
207 245 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
208 246 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
247 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 {'date': '1338 0', 'user': 'test'}
209 248 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 0 {'date': '1340 0', 'user': 'test'}
210 249 $ hg rollback -n
211 250 repository tip rolled back to revision 3 (undo debugobsolete)
212 251 $ hg rollback
213 252 repository tip rolled back to revision 3 (undo debugobsolete)
214 253 $ hg debugobsolete
215 254 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
216 255 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
217 256 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
218 257 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
258 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 {'date': '1338 0', 'user': 'test'}
219 259
220 260 $ cd ..
221 261
222 262 Try to pull markers
223 263
224 264 $ hg init tmpd
225 265 $ hg -R tmpb push tmpd
226 266 pushing to tmpd
227 267 searching for changes
228 268 adding changesets
229 269 adding manifests
230 270 adding file changes
231 271 added 4 changesets with 4 changes to 4 files (+1 heads)
232 272 $ hg -R tmpd debugobsolete
233 273 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
234 274 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
235 275 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
236 276 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
277 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 {'date': '1338 0', 'user': 'test'}
237 278
238 279 Check obsolete keys are exchanged only if source has an obsolete store
239 280
240 281 $ hg init empty
241 282 $ hg --config extensions.debugkeys=debugkeys.py -R empty push tmpd
242 283 pushing to tmpd
243 284 no changes found
244 285 listkeys phases
245 286 listkeys bookmarks
246 287 [1]
247 288
248 289 clone support
249 290 (markers are copied and extinct changesets are included to allow hardlinks)
250 291
251 292 $ hg clone tmpb clone-dest
252 293 updating to branch default
253 294 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
254 295 $ hg -R clone-dest log -G --hidden
255 @ changeset: 5:5601fb93a350
296 @ changeset: 6:6f9641995072
256 297 | tag: tip
257 298 | parent: 1:7c3bad9141dc
258 299 | user: test
259 300 | date: Thu Jan 01 00:00:00 1970 +0000
260 | summary: add new_3_c
301 | summary: add n3w_3_c
302 |
303 | x changeset: 5:5601fb93a350
304 |/ parent: 1:7c3bad9141dc
305 | user: test
306 | date: Thu Jan 01 00:00:00 1970 +0000
307 | summary: add new_3_c
261 308 |
262 309 | x changeset: 4:ca819180edb9
263 310 |/ parent: 1:7c3bad9141dc
264 311 | user: test
265 312 | date: Thu Jan 01 00:00:00 1970 +0000
266 313 | summary: add new_2_c
267 314 |
268 315 | x changeset: 3:cdbce2fbb163
269 316 |/ parent: 1:7c3bad9141dc
270 317 | user: test
271 318 | date: Thu Jan 01 00:00:00 1970 +0000
272 319 | summary: add new_c
273 320 |
274 321 | o changeset: 2:245bde4270cd
275 322 |/ user: test
276 323 | date: Thu Jan 01 00:00:00 1970 +0000
277 324 | summary: add original_c
278 325 |
279 326 o changeset: 1:7c3bad9141dc
280 327 | user: test
281 328 | date: Thu Jan 01 00:00:00 1970 +0000
282 329 | summary: add b
283 330 |
284 331 o changeset: 0:1f0dee641bb7
285 332 user: test
286 333 date: Thu Jan 01 00:00:00 1970 +0000
287 334 summary: add a
288 335
289 336 $ hg -R clone-dest debugobsolete
290 337 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
291 338 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
292 339 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
293 340 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
341 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 {'date': '1338 0', 'user': 'test'}
294 342
295 343
296 344 Destination repo have existing data
297 345 ---------------------------------------
298 346
299 347 On pull
300 348
301 349 $ hg init tmpe
302 350 $ cd tmpe
303 351 $ hg debugobsolete -d '1339 0' 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339
304 352 $ hg pull ../tmpb
305 353 pulling from ../tmpb
306 354 requesting all changes
307 355 adding changesets
308 356 adding manifests
309 357 adding file changes
310 358 added 4 changesets with 4 changes to 4 files (+1 heads)
311 359 (run 'hg heads' to see heads, 'hg merge' to merge)
312 360 $ hg debugobsolete
313 361 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339 0 {'date': '1339 0', 'user': 'test'}
314 362 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
315 363 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
316 364 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
317 365 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
366 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 {'date': '1338 0', 'user': 'test'}
318 367
319 368
320 369 On push
321 370
322 371 $ hg push ../tmpc
323 372 pushing to ../tmpc
324 373 searching for changes
325 374 no changes found
326 375 [1]
327 376 $ hg -R ../tmpc debugobsolete
328 377 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
329 378 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
330 379 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
331 380 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
381 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 {'date': '1338 0', 'user': 'test'}
332 382 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339 0 {'date': '1339 0', 'user': 'test'}
333 383
334 384 detect outgoing obsolete and unstable
335 385 ---------------------------------------
336 386
337 387
338 388 $ hg glog
339 o changeset: 3:5601fb93a350
389 o changeset: 3:6f9641995072
340 390 | tag: tip
341 391 | parent: 1:7c3bad9141dc
342 392 | user: test
343 393 | date: Thu Jan 01 00:00:00 1970 +0000
344 | summary: add new_3_c
394 | summary: add n3w_3_c
345 395 |
346 396 | o changeset: 2:245bde4270cd
347 397 |/ user: test
348 398 | date: Thu Jan 01 00:00:00 1970 +0000
349 399 | summary: add original_c
350 400 |
351 401 o changeset: 1:7c3bad9141dc
352 402 | user: test
353 403 | date: Thu Jan 01 00:00:00 1970 +0000
354 404 | summary: add b
355 405 |
356 406 o changeset: 0:1f0dee641bb7
357 407 user: test
358 408 date: Thu Jan 01 00:00:00 1970 +0000
359 409 summary: add a
360 410
361 $ hg up 'desc("new_3_c")'
411 $ hg up 'desc("n3w_3_c")'
362 412 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
363 413 $ mkcommit original_d
364 414 $ mkcommit original_e
365 415 $ hg debugobsolete `getid original_d` -d '0 0'
366 416 $ hg log -r 'obsolete()'
367 changeset: 4:7c694bff0650
417 changeset: 4:94b33453f93b
368 418 user: test
369 419 date: Thu Jan 01 00:00:00 1970 +0000
370 420 summary: add original_d
371 421
372 422 $ hg glog -r '::unstable()'
373 @ changeset: 5:6e572121998e
423 @ changeset: 5:cda648ca50f5
374 424 | tag: tip
375 425 | user: test
376 426 | date: Thu Jan 01 00:00:00 1970 +0000
377 427 | summary: add original_e
378 428 |
379 x changeset: 4:7c694bff0650
429 x changeset: 4:94b33453f93b
380 430 | user: test
381 431 | date: Thu Jan 01 00:00:00 1970 +0000
382 432 | summary: add original_d
383 433 |
384 o changeset: 3:5601fb93a350
434 o changeset: 3:6f9641995072
385 435 | parent: 1:7c3bad9141dc
386 436 | user: test
387 437 | date: Thu Jan 01 00:00:00 1970 +0000
388 | summary: add new_3_c
438 | summary: add n3w_3_c
389 439 |
390 440 o changeset: 1:7c3bad9141dc
391 441 | user: test
392 442 | date: Thu Jan 01 00:00:00 1970 +0000
393 443 | summary: add b
394 444 |
395 445 o changeset: 0:1f0dee641bb7
396 446 user: test
397 447 date: Thu Jan 01 00:00:00 1970 +0000
398 448 summary: add a
399 449
400 450
401 451 refuse to push obsolete changeset
402 452
403 453 $ hg push ../tmpc/ -r 'desc("original_d")'
404 454 pushing to ../tmpc/
405 455 searching for changes
406 abort: push includes an obsolete changeset: 7c694bff0650!
456 abort: push includes an obsolete changeset: 94b33453f93b!
407 457 [255]
408 458
409 459 refuse to push unstable changeset
410 460
411 461 $ hg push ../tmpc/
412 462 pushing to ../tmpc/
413 463 searching for changes
414 abort: push includes an unstable changeset: 6e572121998e!
464 abort: push includes an unstable changeset: cda648ca50f5!
415 465 [255]
416 466
417 467 Test that extinct changeset are properly detected
418 468
419 469 $ hg log -r 'extinct()'
420 470
421 471 Don't try to push extinct changeset
422 472
423 473 $ hg init ../tmpf
424 474 $ hg out ../tmpf
425 475 comparing with ../tmpf
426 476 searching for changes
427 477 changeset: 0:1f0dee641bb7
428 478 user: test
429 479 date: Thu Jan 01 00:00:00 1970 +0000
430 480 summary: add a
431 481
432 482 changeset: 1:7c3bad9141dc
433 483 user: test
434 484 date: Thu Jan 01 00:00:00 1970 +0000
435 485 summary: add b
436 486
437 487 changeset: 2:245bde4270cd
438 488 user: test
439 489 date: Thu Jan 01 00:00:00 1970 +0000
440 490 summary: add original_c
441 491
442 changeset: 3:5601fb93a350
492 changeset: 3:6f9641995072
443 493 parent: 1:7c3bad9141dc
444 494 user: test
445 495 date: Thu Jan 01 00:00:00 1970 +0000
446 summary: add new_3_c
496 summary: add n3w_3_c
447 497
448 changeset: 4:7c694bff0650
498 changeset: 4:94b33453f93b
449 499 user: test
450 500 date: Thu Jan 01 00:00:00 1970 +0000
451 501 summary: add original_d
452 502
453 changeset: 5:6e572121998e
503 changeset: 5:cda648ca50f5
454 504 tag: tip
455 505 user: test
456 506 date: Thu Jan 01 00:00:00 1970 +0000
457 507 summary: add original_e
458 508
459 509 $ hg push ../tmpf -f # -f because be push unstable too
460 510 pushing to ../tmpf
461 511 searching for changes
462 512 adding changesets
463 513 adding manifests
464 514 adding file changes
465 515 added 6 changesets with 6 changes to 6 files (+1 heads)
466 516
467 517 no warning displayed
468 518
469 519 $ hg push ../tmpf
470 520 pushing to ../tmpf
471 521 searching for changes
472 522 no changes found
473 523 [1]
474 524
475 525 Do not warn about new head when the new head is a successors of a remote one
476 526
477 527 $ hg glog
478 @ changeset: 5:6e572121998e
528 @ changeset: 5:cda648ca50f5
479 529 | tag: tip
480 530 | user: test
481 531 | date: Thu Jan 01 00:00:00 1970 +0000
482 532 | summary: add original_e
483 533 |
484 x changeset: 4:7c694bff0650
534 x changeset: 4:94b33453f93b
485 535 | user: test
486 536 | date: Thu Jan 01 00:00:00 1970 +0000
487 537 | summary: add original_d
488 538 |
489 o changeset: 3:5601fb93a350
539 o changeset: 3:6f9641995072
490 540 | parent: 1:7c3bad9141dc
491 541 | user: test
492 542 | date: Thu Jan 01 00:00:00 1970 +0000
493 | summary: add new_3_c
543 | summary: add n3w_3_c
494 544 |
495 545 | o changeset: 2:245bde4270cd
496 546 |/ user: test
497 547 | date: Thu Jan 01 00:00:00 1970 +0000
498 548 | summary: add original_c
499 549 |
500 550 o changeset: 1:7c3bad9141dc
501 551 | user: test
502 552 | date: Thu Jan 01 00:00:00 1970 +0000
503 553 | summary: add b
504 554 |
505 555 o changeset: 0:1f0dee641bb7
506 556 user: test
507 557 date: Thu Jan 01 00:00:00 1970 +0000
508 558 summary: add a
509 559
510 $ hg up -q 'desc(new_3_c)'
560 $ hg up -q 'desc(n3w_3_c)'
511 561 $ mkcommit obsolete_e
512 562 created new head
513 563 $ hg debugobsolete `getid 'original_e'` `getid 'obsolete_e'`
514 564 $ hg push ../tmpf
515 565 pushing to ../tmpf
516 566 searching for changes
517 567 adding changesets
518 568 adding manifests
519 569 adding file changes
520 570 added 1 changesets with 1 changes to 1 files (+1 heads)
521 571
522 572 Checking _enable=False warning if obsolete marker exists
523 573
524 574 $ echo '[extensions]' >> $HGRCPATH
525 575 $ echo "obs=!" >> $HGRCPATH
526 576 $ hg log -r tip
527 obsolete feature not enabled but 7 markers found!
528 changeset: 6:d6a026544050
577 obsolete feature not enabled but 8 markers found!
578 changeset: 6:3de5eca88c00
529 579 tag: tip
530 parent: 3:5601fb93a350
580 parent: 3:6f9641995072
531 581 user: test
532 582 date: Thu Jan 01 00:00:00 1970 +0000
533 583 summary: add obsolete_e
534 584
General Comments 0
You need to be logged in to leave comments. Login now