##// END OF EJS Templates
bookmarks: cache reverse mapping (issue5868)...
Yuya Nishihara -
r37869:04ceb267 @26 default
parent child Browse files
Show More
@@ -1,945 +1,965 b''
1 1 # Mercurial bookmark support code
2 2 #
3 3 # Copyright 2008 David Soria Parra <dsp@php.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import struct
12 12
13 13 from .i18n import _
14 14 from .node import (
15 15 bin,
16 16 hex,
17 17 short,
18 18 wdirid,
19 19 )
20 20 from . import (
21 21 encoding,
22 22 error,
23 23 obsutil,
24 24 pycompat,
25 25 scmutil,
26 26 txnutil,
27 27 util,
28 28 )
29 29
30 30 # label constants
31 31 # until 3.5, bookmarks.current was the advertised name, not
32 32 # bookmarks.active, so we must use both to avoid breaking old
33 33 # custom styles
34 34 activebookmarklabel = 'bookmarks.active bookmarks.current'
35 35
36 36 def _getbkfile(repo):
37 37 """Hook so that extensions that mess with the store can hook bm storage.
38 38
39 39 For core, this just handles wether we should see pending
40 40 bookmarks or the committed ones. Other extensions (like share)
41 41 may need to tweak this behavior further.
42 42 """
43 43 fp, pending = txnutil.trypending(repo.root, repo.vfs, 'bookmarks')
44 44 return fp
45 45
46 46 class bmstore(object):
47 47 """Storage for bookmarks.
48 48
49 49 This object should do all bookmark-related reads and writes, so
50 50 that it's fairly simple to replace the storage underlying
51 51 bookmarks without having to clone the logic surrounding
52 52 bookmarks. This type also should manage the active bookmark, if
53 53 any.
54 54
55 55 This particular bmstore implementation stores bookmarks as
56 56 {hash}\s{name}\n (the same format as localtags) in
57 57 .hg/bookmarks. The mapping is stored as {name: nodeid}.
58 58 """
59 59
60 60 def __init__(self, repo):
61 61 self._repo = repo
62 62 self._refmap = refmap = {} # refspec: node
63 self._nodemap = nodemap = {} # node: sorted([refspec, ...])
63 64 self._clean = True
64 65 self._aclean = True
65 66 nm = repo.changelog.nodemap
66 67 tonode = bin # force local lookup
67 68 try:
68 69 with _getbkfile(repo) as bkfile:
69 70 for line in bkfile:
70 71 line = line.strip()
71 72 if not line:
72 73 continue
73 74 try:
74 75 sha, refspec = line.split(' ', 1)
75 76 node = tonode(sha)
76 77 if node in nm:
77 78 refspec = encoding.tolocal(refspec)
78 79 refmap[refspec] = node
80 nrefs = nodemap.get(node)
81 if nrefs is None:
82 nodemap[node] = [refspec]
83 else:
84 nrefs.append(refspec)
85 if nrefs[-2] > refspec:
86 # bookmarks weren't sorted before 4.5
87 nrefs.sort()
79 88 except (TypeError, ValueError):
80 89 # TypeError:
81 90 # - bin(...)
82 91 # ValueError:
83 92 # - node in nm, for non-20-bytes entry
84 93 # - split(...), for string without ' '
85 94 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
86 95 % pycompat.bytestr(line))
87 96 except IOError as inst:
88 97 if inst.errno != errno.ENOENT:
89 98 raise
90 99 self._active = _readactive(repo, self)
91 100
92 101 @property
93 102 def active(self):
94 103 return self._active
95 104
96 105 @active.setter
97 106 def active(self, mark):
98 107 if mark is not None and mark not in self._refmap:
99 108 raise AssertionError('bookmark %s does not exist!' % mark)
100 109
101 110 self._active = mark
102 111 self._aclean = False
103 112
104 113 def __len__(self):
105 114 return len(self._refmap)
106 115
107 116 def __iter__(self):
108 117 return iter(self._refmap)
109 118
110 119 def iteritems(self):
111 120 return self._refmap.iteritems()
112 121
113 122 def items(self):
114 123 return self._refmap.items()
115 124
116 125 # TODO: maybe rename to allnames()?
117 126 def keys(self):
118 127 return self._refmap.keys()
119 128
120 129 # TODO: maybe rename to allnodes()? but nodes would have to be deduplicated
130 # could be self._nodemap.keys()
121 131 def values(self):
122 132 return self._refmap.values()
123 133
124 134 def __contains__(self, mark):
125 135 return mark in self._refmap
126 136
127 137 def __getitem__(self, mark):
128 138 return self._refmap[mark]
129 139
130 140 def get(self, mark, default=None):
131 141 return self._refmap.get(mark, default)
132 142
133 143 def _set(self, mark, node):
134 144 self._clean = False
145 if mark in self._refmap:
146 self._del(mark)
135 147 self._refmap[mark] = node
148 nrefs = self._nodemap.get(node)
149 if nrefs is None:
150 self._nodemap[node] = [mark]
151 else:
152 nrefs.append(mark)
153 nrefs.sort()
136 154
137 155 def _del(self, mark):
138 156 self._clean = False
139 del self._refmap[mark]
157 node = self._refmap.pop(mark)
158 nrefs = self._nodemap[node]
159 if len(nrefs) == 1:
160 assert nrefs[0] == mark
161 del self._nodemap[node]
162 else:
163 nrefs.remove(mark)
140 164
141 165 def names(self, node):
142 166 """Return a sorted list of bookmarks pointing to the specified node"""
143 marks = []
144 for m, n in self._refmap.iteritems():
145 if n == node:
146 marks.append(m)
147 return sorted(marks)
167 return self._nodemap.get(node, [])
148 168
149 169 def changectx(self, mark):
150 170 node = self._refmap[mark]
151 171 return self._repo[node]
152 172
153 173 def applychanges(self, repo, tr, changes):
154 174 """Apply a list of changes to bookmarks
155 175 """
156 176 bmchanges = tr.changes.get('bookmarks')
157 177 for name, node in changes:
158 178 old = self._refmap.get(name)
159 179 if node is None:
160 180 self._del(name)
161 181 else:
162 182 self._set(name, node)
163 183 if bmchanges is not None:
164 184 # if a previous value exist preserve the "initial" value
165 185 previous = bmchanges.get(name)
166 186 if previous is not None:
167 187 old = previous[0]
168 188 bmchanges[name] = (old, node)
169 189 self._recordchange(tr)
170 190
171 191 def _recordchange(self, tr):
172 192 """record that bookmarks have been changed in a transaction
173 193
174 194 The transaction is then responsible for updating the file content."""
175 195 tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
176 196 location='plain')
177 197 tr.hookargs['bookmark_moved'] = '1'
178 198
179 199 def _writerepo(self, repo):
180 200 """Factored out for extensibility"""
181 201 rbm = repo._bookmarks
182 202 if rbm.active not in self._refmap:
183 203 rbm.active = None
184 204 rbm._writeactive()
185 205
186 206 with repo.wlock():
187 207 file_ = repo.vfs('bookmarks', 'w', atomictemp=True,
188 208 checkambig=True)
189 209 try:
190 210 self._write(file_)
191 211 except: # re-raises
192 212 file_.discard()
193 213 raise
194 214 finally:
195 215 file_.close()
196 216
197 217 def _writeactive(self):
198 218 if self._aclean:
199 219 return
200 220 with self._repo.wlock():
201 221 if self._active is not None:
202 222 f = self._repo.vfs('bookmarks.current', 'w', atomictemp=True,
203 223 checkambig=True)
204 224 try:
205 225 f.write(encoding.fromlocal(self._active))
206 226 finally:
207 227 f.close()
208 228 else:
209 229 self._repo.vfs.tryunlink('bookmarks.current')
210 230 self._aclean = True
211 231
212 232 def _write(self, fp):
213 233 for name, node in sorted(self._refmap.iteritems()):
214 234 fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
215 235 self._clean = True
216 236 self._repo.invalidatevolatilesets()
217 237
218 238 def expandname(self, bname):
219 239 if bname == '.':
220 240 if self.active:
221 241 return self.active
222 242 else:
223 243 raise error.Abort(_("no active bookmark"))
224 244 return bname
225 245
226 246 def checkconflict(self, mark, force=False, target=None):
227 247 """check repo for a potential clash of mark with an existing bookmark,
228 248 branch, or hash
229 249
230 250 If target is supplied, then check that we are moving the bookmark
231 251 forward.
232 252
233 253 If force is supplied, then forcibly move the bookmark to a new commit
234 254 regardless if it is a move forward.
235 255
236 256 If divergent bookmark are to be deleted, they will be returned as list.
237 257 """
238 258 cur = self._repo['.'].node()
239 259 if mark in self._refmap and not force:
240 260 if target:
241 261 if self._refmap[mark] == target and target == cur:
242 262 # re-activating a bookmark
243 263 return []
244 264 rev = self._repo[target].rev()
245 265 anc = self._repo.changelog.ancestors([rev])
246 266 bmctx = self.changectx(mark)
247 267 divs = [self._refmap[b] for b in self._refmap
248 268 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
249 269
250 270 # allow resolving a single divergent bookmark even if moving
251 271 # the bookmark across branches when a revision is specified
252 272 # that contains a divergent bookmark
253 273 if bmctx.rev() not in anc and target in divs:
254 274 return divergent2delete(self._repo, [target], mark)
255 275
256 276 deletefrom = [b for b in divs
257 277 if self._repo[b].rev() in anc or b == target]
258 278 delbms = divergent2delete(self._repo, deletefrom, mark)
259 279 if validdest(self._repo, bmctx, self._repo[target]):
260 280 self._repo.ui.status(
261 281 _("moving bookmark '%s' forward from %s\n") %
262 282 (mark, short(bmctx.node())))
263 283 return delbms
264 284 raise error.Abort(_("bookmark '%s' already exists "
265 285 "(use -f to force)") % mark)
266 286 if ((mark in self._repo.branchmap() or
267 287 mark == self._repo.dirstate.branch()) and not force):
268 288 raise error.Abort(
269 289 _("a bookmark cannot have the name of an existing branch"))
270 290 if len(mark) > 3 and not force:
271 291 try:
272 292 shadowhash = scmutil.isrevsymbol(self._repo, mark)
273 293 except error.LookupError: # ambiguous identifier
274 294 shadowhash = False
275 295 if shadowhash:
276 296 self._repo.ui.warn(
277 297 _("bookmark %s matches a changeset hash\n"
278 298 "(did you leave a -r out of an 'hg bookmark' "
279 299 "command?)\n")
280 300 % mark)
281 301 return []
282 302
283 303 def _readactive(repo, marks):
284 304 """
285 305 Get the active bookmark. We can have an active bookmark that updates
286 306 itself as we commit. This function returns the name of that bookmark.
287 307 It is stored in .hg/bookmarks.current
288 308 """
289 309 mark = None
290 310 try:
291 311 file = repo.vfs('bookmarks.current')
292 312 except IOError as inst:
293 313 if inst.errno != errno.ENOENT:
294 314 raise
295 315 return None
296 316 try:
297 317 # No readline() in osutil.posixfile, reading everything is
298 318 # cheap.
299 319 # Note that it's possible for readlines() here to raise
300 320 # IOError, since we might be reading the active mark over
301 321 # static-http which only tries to load the file when we try
302 322 # to read from it.
303 323 mark = encoding.tolocal((file.readlines() or [''])[0])
304 324 if mark == '' or mark not in marks:
305 325 mark = None
306 326 except IOError as inst:
307 327 if inst.errno != errno.ENOENT:
308 328 raise
309 329 return None
310 330 finally:
311 331 file.close()
312 332 return mark
313 333
314 334 def activate(repo, mark):
315 335 """
316 336 Set the given bookmark to be 'active', meaning that this bookmark will
317 337 follow new commits that are made.
318 338 The name is recorded in .hg/bookmarks.current
319 339 """
320 340 repo._bookmarks.active = mark
321 341 repo._bookmarks._writeactive()
322 342
323 343 def deactivate(repo):
324 344 """
325 345 Unset the active bookmark in this repository.
326 346 """
327 347 repo._bookmarks.active = None
328 348 repo._bookmarks._writeactive()
329 349
330 350 def isactivewdirparent(repo):
331 351 """
332 352 Tell whether the 'active' bookmark (the one that follows new commits)
333 353 points to one of the parents of the current working directory (wdir).
334 354
335 355 While this is normally the case, it can on occasion be false; for example,
336 356 immediately after a pull, the active bookmark can be moved to point
337 357 to a place different than the wdir. This is solved by running `hg update`.
338 358 """
339 359 mark = repo._activebookmark
340 360 marks = repo._bookmarks
341 361 parents = [p.node() for p in repo[None].parents()]
342 362 return (mark in marks and marks[mark] in parents)
343 363
344 364 def divergent2delete(repo, deletefrom, bm):
345 365 """find divergent versions of bm on nodes in deletefrom.
346 366
347 367 the list of bookmark to delete."""
348 368 todelete = []
349 369 marks = repo._bookmarks
350 370 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
351 371 for mark in divergent:
352 372 if mark == '@' or '@' not in mark:
353 373 # can't be divergent by definition
354 374 continue
355 375 if mark and marks[mark] in deletefrom:
356 376 if mark != bm:
357 377 todelete.append(mark)
358 378 return todelete
359 379
360 380 def headsforactive(repo):
361 381 """Given a repo with an active bookmark, return divergent bookmark nodes.
362 382
363 383 Args:
364 384 repo: A repository with an active bookmark.
365 385
366 386 Returns:
367 387 A list of binary node ids that is the full list of other
368 388 revisions with bookmarks divergent from the active bookmark. If
369 389 there were no divergent bookmarks, then this list will contain
370 390 only one entry.
371 391 """
372 392 if not repo._activebookmark:
373 393 raise ValueError(
374 394 'headsforactive() only makes sense with an active bookmark')
375 395 name = repo._activebookmark.split('@', 1)[0]
376 396 heads = []
377 397 for mark, n in repo._bookmarks.iteritems():
378 398 if mark.split('@', 1)[0] == name:
379 399 heads.append(n)
380 400 return heads
381 401
382 402 def calculateupdate(ui, repo):
383 403 '''Return a tuple (activemark, movemarkfrom) indicating the active bookmark
384 404 and where to move the active bookmark from, if needed.'''
385 405 checkout, movemarkfrom = None, None
386 406 activemark = repo._activebookmark
387 407 if isactivewdirparent(repo):
388 408 movemarkfrom = repo['.'].node()
389 409 elif activemark:
390 410 ui.status(_("updating to active bookmark %s\n") % activemark)
391 411 checkout = activemark
392 412 return (checkout, movemarkfrom)
393 413
394 414 def update(repo, parents, node):
395 415 deletefrom = parents
396 416 marks = repo._bookmarks
397 417 active = marks.active
398 418 if not active:
399 419 return False
400 420
401 421 bmchanges = []
402 422 if marks[active] in parents:
403 423 new = repo[node]
404 424 divs = [marks.changectx(b) for b in marks
405 425 if b.split('@', 1)[0] == active.split('@', 1)[0]]
406 426 anc = repo.changelog.ancestors([new.rev()])
407 427 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
408 428 if validdest(repo, marks.changectx(active), new):
409 429 bmchanges.append((active, new.node()))
410 430
411 431 for bm in divergent2delete(repo, deletefrom, active):
412 432 bmchanges.append((bm, None))
413 433
414 434 if bmchanges:
415 435 with repo.lock(), repo.transaction('bookmark') as tr:
416 436 marks.applychanges(repo, tr, bmchanges)
417 437 return bool(bmchanges)
418 438
419 439 def listbinbookmarks(repo):
420 440 # We may try to list bookmarks on a repo type that does not
421 441 # support it (e.g., statichttprepository).
422 442 marks = getattr(repo, '_bookmarks', {})
423 443
424 444 hasnode = repo.changelog.hasnode
425 445 for k, v in marks.iteritems():
426 446 # don't expose local divergent bookmarks
427 447 if hasnode(v) and ('@' not in k or k.endswith('@')):
428 448 yield k, v
429 449
430 450 def listbookmarks(repo):
431 451 d = {}
432 452 for book, node in listbinbookmarks(repo):
433 453 d[book] = hex(node)
434 454 return d
435 455
436 456 def pushbookmark(repo, key, old, new):
437 457 with repo.wlock(), repo.lock(), repo.transaction('bookmarks') as tr:
438 458 marks = repo._bookmarks
439 459 existing = hex(marks.get(key, ''))
440 460 if existing != old and existing != new:
441 461 return False
442 462 if new == '':
443 463 changes = [(key, None)]
444 464 else:
445 465 if new not in repo:
446 466 return False
447 467 changes = [(key, repo[new].node())]
448 468 marks.applychanges(repo, tr, changes)
449 469 return True
450 470
451 471 def comparebookmarks(repo, srcmarks, dstmarks, targets=None):
452 472 '''Compare bookmarks between srcmarks and dstmarks
453 473
454 474 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
455 475 differ, invalid)", each are list of bookmarks below:
456 476
457 477 :addsrc: added on src side (removed on dst side, perhaps)
458 478 :adddst: added on dst side (removed on src side, perhaps)
459 479 :advsrc: advanced on src side
460 480 :advdst: advanced on dst side
461 481 :diverge: diverge
462 482 :differ: changed, but changeset referred on src is unknown on dst
463 483 :invalid: unknown on both side
464 484 :same: same on both side
465 485
466 486 Each elements of lists in result tuple is tuple "(bookmark name,
467 487 changeset ID on source side, changeset ID on destination
468 488 side)". Each changeset IDs are 40 hexadecimal digit string or
469 489 None.
470 490
471 491 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
472 492 "invalid" list may be unknown for repo.
473 493
474 494 If "targets" is specified, only bookmarks listed in it are
475 495 examined.
476 496 '''
477 497
478 498 if targets:
479 499 bset = set(targets)
480 500 else:
481 501 srcmarkset = set(srcmarks)
482 502 dstmarkset = set(dstmarks)
483 503 bset = srcmarkset | dstmarkset
484 504
485 505 results = ([], [], [], [], [], [], [], [])
486 506 addsrc = results[0].append
487 507 adddst = results[1].append
488 508 advsrc = results[2].append
489 509 advdst = results[3].append
490 510 diverge = results[4].append
491 511 differ = results[5].append
492 512 invalid = results[6].append
493 513 same = results[7].append
494 514
495 515 for b in sorted(bset):
496 516 if b not in srcmarks:
497 517 if b in dstmarks:
498 518 adddst((b, None, dstmarks[b]))
499 519 else:
500 520 invalid((b, None, None))
501 521 elif b not in dstmarks:
502 522 addsrc((b, srcmarks[b], None))
503 523 else:
504 524 scid = srcmarks[b]
505 525 dcid = dstmarks[b]
506 526 if scid == dcid:
507 527 same((b, scid, dcid))
508 528 elif scid in repo and dcid in repo:
509 529 sctx = repo[scid]
510 530 dctx = repo[dcid]
511 531 if sctx.rev() < dctx.rev():
512 532 if validdest(repo, sctx, dctx):
513 533 advdst((b, scid, dcid))
514 534 else:
515 535 diverge((b, scid, dcid))
516 536 else:
517 537 if validdest(repo, dctx, sctx):
518 538 advsrc((b, scid, dcid))
519 539 else:
520 540 diverge((b, scid, dcid))
521 541 else:
522 542 # it is too expensive to examine in detail, in this case
523 543 differ((b, scid, dcid))
524 544
525 545 return results
526 546
527 547 def _diverge(ui, b, path, localmarks, remotenode):
528 548 '''Return appropriate diverged bookmark for specified ``path``
529 549
530 550 This returns None, if it is failed to assign any divergent
531 551 bookmark name.
532 552
533 553 This reuses already existing one with "@number" suffix, if it
534 554 refers ``remotenode``.
535 555 '''
536 556 if b == '@':
537 557 b = ''
538 558 # try to use an @pathalias suffix
539 559 # if an @pathalias already exists, we overwrite (update) it
540 560 if path.startswith("file:"):
541 561 path = util.url(path).path
542 562 for p, u in ui.configitems("paths"):
543 563 if u.startswith("file:"):
544 564 u = util.url(u).path
545 565 if path == u:
546 566 return '%s@%s' % (b, p)
547 567
548 568 # assign a unique "@number" suffix newly
549 569 for x in range(1, 100):
550 570 n = '%s@%d' % (b, x)
551 571 if n not in localmarks or localmarks[n] == remotenode:
552 572 return n
553 573
554 574 return None
555 575
556 576 def unhexlifybookmarks(marks):
557 577 binremotemarks = {}
558 578 for name, node in marks.items():
559 579 binremotemarks[name] = bin(node)
560 580 return binremotemarks
561 581
562 582 _binaryentry = struct.Struct('>20sH')
563 583
564 584 def binaryencode(bookmarks):
565 585 """encode a '(bookmark, node)' iterable into a binary stream
566 586
567 587 the binary format is:
568 588
569 589 <node><bookmark-length><bookmark-name>
570 590
571 591 :node: is a 20 bytes binary node,
572 592 :bookmark-length: an unsigned short,
573 593 :bookmark-name: the name of the bookmark (of length <bookmark-length>)
574 594
575 595 wdirid (all bits set) will be used as a special value for "missing"
576 596 """
577 597 binarydata = []
578 598 for book, node in bookmarks:
579 599 if not node: # None or ''
580 600 node = wdirid
581 601 binarydata.append(_binaryentry.pack(node, len(book)))
582 602 binarydata.append(book)
583 603 return ''.join(binarydata)
584 604
585 605 def binarydecode(stream):
586 606 """decode a binary stream into an '(bookmark, node)' iterable
587 607
588 608 the binary format is:
589 609
590 610 <node><bookmark-length><bookmark-name>
591 611
592 612 :node: is a 20 bytes binary node,
593 613 :bookmark-length: an unsigned short,
594 614 :bookmark-name: the name of the bookmark (of length <bookmark-length>))
595 615
596 616 wdirid (all bits set) will be used as a special value for "missing"
597 617 """
598 618 entrysize = _binaryentry.size
599 619 books = []
600 620 while True:
601 621 entry = stream.read(entrysize)
602 622 if len(entry) < entrysize:
603 623 if entry:
604 624 raise error.Abort(_('bad bookmark stream'))
605 625 break
606 626 node, length = _binaryentry.unpack(entry)
607 627 bookmark = stream.read(length)
608 628 if len(bookmark) < length:
609 629 if entry:
610 630 raise error.Abort(_('bad bookmark stream'))
611 631 if node == wdirid:
612 632 node = None
613 633 books.append((bookmark, node))
614 634 return books
615 635
616 636 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
617 637 ui.debug("checking for updated bookmarks\n")
618 638 localmarks = repo._bookmarks
619 639 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
620 640 ) = comparebookmarks(repo, remotemarks, localmarks)
621 641
622 642 status = ui.status
623 643 warn = ui.warn
624 644 if ui.configbool('ui', 'quietbookmarkmove'):
625 645 status = warn = ui.debug
626 646
627 647 explicit = set(explicit)
628 648 changed = []
629 649 for b, scid, dcid in addsrc:
630 650 if scid in repo: # add remote bookmarks for changes we already have
631 651 changed.append((b, scid, status,
632 652 _("adding remote bookmark %s\n") % (b)))
633 653 elif b in explicit:
634 654 explicit.remove(b)
635 655 ui.warn(_("remote bookmark %s points to locally missing %s\n")
636 656 % (b, hex(scid)[:12]))
637 657
638 658 for b, scid, dcid in advsrc:
639 659 changed.append((b, scid, status,
640 660 _("updating bookmark %s\n") % (b)))
641 661 # remove normal movement from explicit set
642 662 explicit.difference_update(d[0] for d in changed)
643 663
644 664 for b, scid, dcid in diverge:
645 665 if b in explicit:
646 666 explicit.discard(b)
647 667 changed.append((b, scid, status,
648 668 _("importing bookmark %s\n") % (b)))
649 669 else:
650 670 db = _diverge(ui, b, path, localmarks, scid)
651 671 if db:
652 672 changed.append((db, scid, warn,
653 673 _("divergent bookmark %s stored as %s\n") %
654 674 (b, db)))
655 675 else:
656 676 warn(_("warning: failed to assign numbered name "
657 677 "to divergent bookmark %s\n") % (b))
658 678 for b, scid, dcid in adddst + advdst:
659 679 if b in explicit:
660 680 explicit.discard(b)
661 681 changed.append((b, scid, status,
662 682 _("importing bookmark %s\n") % (b)))
663 683 for b, scid, dcid in differ:
664 684 if b in explicit:
665 685 explicit.remove(b)
666 686 ui.warn(_("remote bookmark %s points to locally missing %s\n")
667 687 % (b, hex(scid)[:12]))
668 688
669 689 if changed:
670 690 tr = trfunc()
671 691 changes = []
672 692 for b, node, writer, msg in sorted(changed):
673 693 changes.append((b, node))
674 694 writer(msg)
675 695 localmarks.applychanges(repo, tr, changes)
676 696
677 697 def incoming(ui, repo, peer):
678 698 '''Show bookmarks incoming from other to repo
679 699 '''
680 700 ui.status(_("searching for changed bookmarks\n"))
681 701
682 702 with peer.commandexecutor() as e:
683 703 remotemarks = unhexlifybookmarks(e.callcommand('listkeys', {
684 704 'namespace': 'bookmarks',
685 705 }).result())
686 706
687 707 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
688 708 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
689 709
690 710 incomings = []
691 711 if ui.debugflag:
692 712 getid = lambda id: id
693 713 else:
694 714 getid = lambda id: id[:12]
695 715 if ui.verbose:
696 716 def add(b, id, st):
697 717 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
698 718 else:
699 719 def add(b, id, st):
700 720 incomings.append(" %-25s %s\n" % (b, getid(id)))
701 721 for b, scid, dcid in addsrc:
702 722 # i18n: "added" refers to a bookmark
703 723 add(b, hex(scid), _('added'))
704 724 for b, scid, dcid in advsrc:
705 725 # i18n: "advanced" refers to a bookmark
706 726 add(b, hex(scid), _('advanced'))
707 727 for b, scid, dcid in diverge:
708 728 # i18n: "diverged" refers to a bookmark
709 729 add(b, hex(scid), _('diverged'))
710 730 for b, scid, dcid in differ:
711 731 # i18n: "changed" refers to a bookmark
712 732 add(b, hex(scid), _('changed'))
713 733
714 734 if not incomings:
715 735 ui.status(_("no changed bookmarks found\n"))
716 736 return 1
717 737
718 738 for s in sorted(incomings):
719 739 ui.write(s)
720 740
721 741 return 0
722 742
723 743 def outgoing(ui, repo, other):
724 744 '''Show bookmarks outgoing from repo to other
725 745 '''
726 746 ui.status(_("searching for changed bookmarks\n"))
727 747
728 748 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
729 749 r = comparebookmarks(repo, repo._bookmarks, remotemarks)
730 750 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
731 751
732 752 outgoings = []
733 753 if ui.debugflag:
734 754 getid = lambda id: id
735 755 else:
736 756 getid = lambda id: id[:12]
737 757 if ui.verbose:
738 758 def add(b, id, st):
739 759 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
740 760 else:
741 761 def add(b, id, st):
742 762 outgoings.append(" %-25s %s\n" % (b, getid(id)))
743 763 for b, scid, dcid in addsrc:
744 764 # i18n: "added refers to a bookmark
745 765 add(b, hex(scid), _('added'))
746 766 for b, scid, dcid in adddst:
747 767 # i18n: "deleted" refers to a bookmark
748 768 add(b, ' ' * 40, _('deleted'))
749 769 for b, scid, dcid in advsrc:
750 770 # i18n: "advanced" refers to a bookmark
751 771 add(b, hex(scid), _('advanced'))
752 772 for b, scid, dcid in diverge:
753 773 # i18n: "diverged" refers to a bookmark
754 774 add(b, hex(scid), _('diverged'))
755 775 for b, scid, dcid in differ:
756 776 # i18n: "changed" refers to a bookmark
757 777 add(b, hex(scid), _('changed'))
758 778
759 779 if not outgoings:
760 780 ui.status(_("no changed bookmarks found\n"))
761 781 return 1
762 782
763 783 for s in sorted(outgoings):
764 784 ui.write(s)
765 785
766 786 return 0
767 787
768 788 def summary(repo, peer):
769 789 '''Compare bookmarks between repo and other for "hg summary" output
770 790
771 791 This returns "(# of incoming, # of outgoing)" tuple.
772 792 '''
773 793 with peer.commandexecutor() as e:
774 794 remotemarks = unhexlifybookmarks(e.callcommand('listkeys', {
775 795 'namespace': 'bookmarks',
776 796 }).result())
777 797
778 798 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
779 799 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
780 800 return (len(addsrc), len(adddst))
781 801
782 802 def validdest(repo, old, new):
783 803 """Is the new bookmark destination a valid update from the old one"""
784 804 repo = repo.unfiltered()
785 805 if old == new:
786 806 # Old == new -> nothing to update.
787 807 return False
788 808 elif not old:
789 809 # old is nullrev, anything is valid.
790 810 # (new != nullrev has been excluded by the previous check)
791 811 return True
792 812 elif repo.obsstore:
793 813 return new.node() in obsutil.foreground(repo, [old.node()])
794 814 else:
795 815 # still an independent clause as it is lazier (and therefore faster)
796 816 return old.descendant(new)
797 817
798 818 def checkformat(repo, mark):
799 819 """return a valid version of a potential bookmark name
800 820
801 821 Raises an abort error if the bookmark name is not valid.
802 822 """
803 823 mark = mark.strip()
804 824 if not mark:
805 825 raise error.Abort(_("bookmark names cannot consist entirely of "
806 826 "whitespace"))
807 827 scmutil.checknewlabel(repo, mark, 'bookmark')
808 828 return mark
809 829
810 830 def delete(repo, tr, names):
811 831 """remove a mark from the bookmark store
812 832
813 833 Raises an abort error if mark does not exist.
814 834 """
815 835 marks = repo._bookmarks
816 836 changes = []
817 837 for mark in names:
818 838 if mark not in marks:
819 839 raise error.Abort(_("bookmark '%s' does not exist") % mark)
820 840 if mark == repo._activebookmark:
821 841 deactivate(repo)
822 842 changes.append((mark, None))
823 843 marks.applychanges(repo, tr, changes)
824 844
825 845 def rename(repo, tr, old, new, force=False, inactive=False):
826 846 """rename a bookmark from old to new
827 847
828 848 If force is specified, then the new name can overwrite an existing
829 849 bookmark.
830 850
831 851 If inactive is specified, then do not activate the new bookmark.
832 852
833 853 Raises an abort error if old is not in the bookmark store.
834 854 """
835 855 marks = repo._bookmarks
836 856 mark = checkformat(repo, new)
837 857 if old not in marks:
838 858 raise error.Abort(_("bookmark '%s' does not exist") % old)
839 859 changes = []
840 860 for bm in marks.checkconflict(mark, force):
841 861 changes.append((bm, None))
842 862 changes.extend([(mark, marks[old]), (old, None)])
843 863 marks.applychanges(repo, tr, changes)
844 864 if repo._activebookmark == old and not inactive:
845 865 activate(repo, mark)
846 866
847 867 def addbookmarks(repo, tr, names, rev=None, force=False, inactive=False):
848 868 """add a list of bookmarks
849 869
850 870 If force is specified, then the new name can overwrite an existing
851 871 bookmark.
852 872
853 873 If inactive is specified, then do not activate any bookmark. Otherwise, the
854 874 first bookmark is activated.
855 875
856 876 Raises an abort error if old is not in the bookmark store.
857 877 """
858 878 marks = repo._bookmarks
859 879 cur = repo['.'].node()
860 880 newact = None
861 881 changes = []
862 882 hiddenrev = None
863 883
864 884 # unhide revs if any
865 885 if rev:
866 886 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
867 887
868 888 for mark in names:
869 889 mark = checkformat(repo, mark)
870 890 if newact is None:
871 891 newact = mark
872 892 if inactive and mark == repo._activebookmark:
873 893 deactivate(repo)
874 894 return
875 895 tgt = cur
876 896 if rev:
877 897 ctx = scmutil.revsingle(repo, rev)
878 898 if ctx.hidden():
879 899 hiddenrev = ctx.hex()[:12]
880 900 tgt = ctx.node()
881 901 for bm in marks.checkconflict(mark, force, tgt):
882 902 changes.append((bm, None))
883 903 changes.append((mark, tgt))
884 904
885 905 if hiddenrev:
886 906 repo.ui.warn(_("bookmarking hidden changeset %s\n") % hiddenrev)
887 907
888 908 if ctx.obsolete():
889 909 msg = obsutil._getfilteredreason(repo, "%s" % hiddenrev, ctx)
890 910 repo.ui.warn("(%s)\n" % msg)
891 911
892 912 marks.applychanges(repo, tr, changes)
893 913 if not inactive and cur == marks[newact] and not rev:
894 914 activate(repo, newact)
895 915 elif cur != tgt and newact == repo._activebookmark:
896 916 deactivate(repo)
897 917
898 918 def _printbookmarks(ui, repo, bmarks, **opts):
899 919 """private method to print bookmarks
900 920
901 921 Provides a way for extensions to control how bookmarks are printed (e.g.
902 922 prepend or postpend names)
903 923 """
904 924 opts = pycompat.byteskwargs(opts)
905 925 fm = ui.formatter('bookmarks', opts)
906 926 hexfn = fm.hexfunc
907 927 if len(bmarks) == 0 and fm.isplain():
908 928 ui.status(_("no bookmarks set\n"))
909 929 for bmark, (n, prefix, label) in sorted(bmarks.iteritems()):
910 930 fm.startitem()
911 931 if not ui.quiet:
912 932 fm.plain(' %s ' % prefix, label=label)
913 933 fm.write('bookmark', '%s', bmark, label=label)
914 934 pad = " " * (25 - encoding.colwidth(bmark))
915 935 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
916 936 repo.changelog.rev(n), hexfn(n), label=label)
917 937 fm.data(active=(activebookmarklabel in label))
918 938 fm.plain('\n')
919 939 fm.end()
920 940
921 941 def printbookmarks(ui, repo, **opts):
922 942 """print bookmarks to a formatter
923 943
924 944 Provides a way for extensions to control how bookmarks are printed.
925 945 """
926 946 marks = repo._bookmarks
927 947 bmarks = {}
928 948 for bmark, n in sorted(marks.iteritems()):
929 949 active = repo._activebookmark
930 950 if bmark == active:
931 951 prefix, label = '*', activebookmarklabel
932 952 else:
933 953 prefix, label = ' ', ''
934 954
935 955 bmarks[bmark] = (n, prefix, label)
936 956 _printbookmarks(ui, repo, bmarks, **opts)
937 957
938 958 def preparehookargs(name, old, new):
939 959 if new is None:
940 960 new = ''
941 961 if old is None:
942 962 old = ''
943 963 return {'bookmark': name,
944 964 'node': hex(new),
945 965 'oldnode': hex(old)}
@@ -1,1164 +1,1177 b''
1 1
2 2 $ hg init repo
3 3 $ cd repo
4 4
5 5 $ cat > $TESTTMP/hook.sh <<'EOF'
6 6 > echo "test-hook-bookmark: $HG_BOOKMARK: $HG_OLDNODE -> $HG_NODE"
7 7 > EOF
8 8 $ TESTHOOK="hooks.txnclose-bookmark.test=sh $TESTTMP/hook.sh"
9 9
10 10 no bookmarks
11 11
12 12 $ hg bookmarks
13 13 no bookmarks set
14 14
15 15 $ hg bookmarks -Tjson
16 16 [
17 17 ]
18 18
19 19 bookmark rev -1
20 20
21 21 $ hg bookmark X --config "$TESTHOOK"
22 22 test-hook-bookmark: X: -> 0000000000000000000000000000000000000000
23 23
24 24 list bookmarks
25 25
26 26 $ hg bookmarks
27 27 * X -1:000000000000
28 28
29 29 list bookmarks with color
30 30
31 31 $ hg --config extensions.color= --config color.mode=ansi \
32 32 > bookmarks --color=always
33 33 \x1b[0;32m * \x1b[0m\x1b[0;32mX\x1b[0m\x1b[0;32m -1:000000000000\x1b[0m (esc)
34 34
35 35 $ echo a > a
36 36 $ hg add a
37 37 $ hg commit -m 0 --config "$TESTHOOK"
38 38 test-hook-bookmark: X: 0000000000000000000000000000000000000000 -> f7b1eb17ad24730a1651fccd46c43826d1bbc2ac
39 39
40 40 bookmark X moved to rev 0
41 41
42 42 $ hg bookmarks
43 43 * X 0:f7b1eb17ad24
44 44
45 45 look up bookmark
46 46
47 47 $ hg log -r X
48 48 changeset: 0:f7b1eb17ad24
49 49 bookmark: X
50 50 tag: tip
51 51 user: test
52 52 date: Thu Jan 01 00:00:00 1970 +0000
53 53 summary: 0
54 54
55 55
56 56 second bookmark for rev 0, command should work even with ui.strict on
57 57
58 58 $ hg --config ui.strict=1 bookmark X2 --config "$TESTHOOK"
59 59 test-hook-bookmark: X2: -> f7b1eb17ad24730a1651fccd46c43826d1bbc2ac
60 60
61 61 bookmark rev -1 again
62 62
63 63 $ hg bookmark -r null Y
64 64
65 65 list bookmarks
66 66
67 67 $ hg bookmarks
68 68 X 0:f7b1eb17ad24
69 69 * X2 0:f7b1eb17ad24
70 70 Y -1:000000000000
71 $ hg log -T '{bookmarks % "{rev} {bookmark}\n"}'
72 0 X
73 0 X2
71 74
72 75 $ echo b > b
73 76 $ hg add b
74 77 $ hg commit -m 1 --config "$TESTHOOK"
75 78 test-hook-bookmark: X2: f7b1eb17ad24730a1651fccd46c43826d1bbc2ac -> 925d80f479bb026b0fb3deb27503780b13f74123
76 79
77 80 $ hg bookmarks -Tjson
78 81 [
79 82 {
80 83 "active": false,
81 84 "bookmark": "X",
82 85 "node": "f7b1eb17ad24730a1651fccd46c43826d1bbc2ac",
83 86 "rev": 0
84 87 },
85 88 {
86 89 "active": true,
87 90 "bookmark": "X2",
88 91 "node": "925d80f479bb026b0fb3deb27503780b13f74123",
89 92 "rev": 1
90 93 },
91 94 {
92 95 "active": false,
93 96 "bookmark": "Y",
94 97 "node": "0000000000000000000000000000000000000000",
95 98 "rev": -1
96 99 }
97 100 ]
98 101
99 102 bookmarks revset
100 103
101 104 $ hg log -r 'bookmark()'
102 105 changeset: 0:f7b1eb17ad24
103 106 bookmark: X
104 107 user: test
105 108 date: Thu Jan 01 00:00:00 1970 +0000
106 109 summary: 0
107 110
108 111 changeset: 1:925d80f479bb
109 112 bookmark: X2
110 113 tag: tip
111 114 user: test
112 115 date: Thu Jan 01 00:00:00 1970 +0000
113 116 summary: 1
114 117
115 118 $ hg log -r 'bookmark(Y)'
116 119 $ hg log -r 'bookmark(X2)'
117 120 changeset: 1:925d80f479bb
118 121 bookmark: X2
119 122 tag: tip
120 123 user: test
121 124 date: Thu Jan 01 00:00:00 1970 +0000
122 125 summary: 1
123 126
124 127 $ hg log -r 'bookmark("re:X")'
125 128 changeset: 0:f7b1eb17ad24
126 129 bookmark: X
127 130 user: test
128 131 date: Thu Jan 01 00:00:00 1970 +0000
129 132 summary: 0
130 133
131 134 changeset: 1:925d80f479bb
132 135 bookmark: X2
133 136 tag: tip
134 137 user: test
135 138 date: Thu Jan 01 00:00:00 1970 +0000
136 139 summary: 1
137 140
138 141 $ hg log -r 'bookmark("literal:X")'
139 142 changeset: 0:f7b1eb17ad24
140 143 bookmark: X
141 144 user: test
142 145 date: Thu Jan 01 00:00:00 1970 +0000
143 146 summary: 0
144 147
145 148
146 149 $ hg log -r 'bookmark(unknown)'
147 150 abort: bookmark 'unknown' does not exist!
148 151 [255]
149 152 $ hg log -r 'bookmark("literal:unknown")'
150 153 abort: bookmark 'unknown' does not exist!
151 154 [255]
152 155 $ hg log -r 'bookmark("re:unknown")'
153 156 abort: no bookmarks exist that match 'unknown'!
154 157 [255]
155 158 $ hg log -r 'present(bookmark("literal:unknown"))'
156 159 $ hg log -r 'present(bookmark("re:unknown"))'
157 160
158 161 $ hg help revsets | grep 'bookmark('
159 162 "bookmark([name])"
160 163
161 164 bookmarks X and X2 moved to rev 1, Y at rev -1
162 165
163 166 $ hg bookmarks
164 167 X 0:f7b1eb17ad24
165 168 * X2 1:925d80f479bb
166 169 Y -1:000000000000
167 170
168 171 bookmark rev 0 again
169 172
170 173 $ hg bookmark -r 0 Z
171 174
172 175 $ hg update X
173 176 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
174 177 (activating bookmark X)
175 178 $ echo c > c
176 179 $ hg add c
177 180 $ hg commit -m 2
178 181 created new head
179 182
180 183 bookmarks X moved to rev 2, Y at rev -1, Z at rev 0
181 184
182 185 $ hg bookmarks
183 186 * X 2:db815d6d32e6
184 187 X2 1:925d80f479bb
185 188 Y -1:000000000000
186 189 Z 0:f7b1eb17ad24
187 190
188 191 rename nonexistent bookmark
189 192
190 193 $ hg bookmark -m A B
191 194 abort: bookmark 'A' does not exist
192 195 [255]
193 196
194 197 rename to existent bookmark
195 198
196 199 $ hg bookmark -m X Y
197 200 abort: bookmark 'Y' already exists (use -f to force)
198 201 [255]
199 202
200 203 force rename to existent bookmark
201 204
202 205 $ hg bookmark -f -m X Y
203 206
204 207 rename bookmark using .
205 208
206 209 $ hg book rename-me
207 210 $ hg book -m . renamed --config "$TESTHOOK"
208 211 test-hook-bookmark: rename-me: db815d6d32e69058eadefc8cffbad37675707975 ->
209 212 test-hook-bookmark: renamed: -> db815d6d32e69058eadefc8cffbad37675707975
210 213 $ hg bookmark
211 214 X2 1:925d80f479bb
212 215 Y 2:db815d6d32e6
213 216 Z 0:f7b1eb17ad24
214 217 * renamed 2:db815d6d32e6
215 218 $ hg up -q Y
216 219 $ hg book -d renamed --config "$TESTHOOK"
217 220 test-hook-bookmark: renamed: db815d6d32e69058eadefc8cffbad37675707975 ->
218 221
219 222 rename bookmark using . with no active bookmark
220 223
221 224 $ hg book rename-me
222 225 $ hg book -i rename-me
223 226 $ hg book -m . renamed
224 227 abort: no active bookmark
225 228 [255]
226 229 $ hg up -q Y
227 230 $ hg book -d rename-me
228 231
229 232 delete bookmark using .
230 233
231 234 $ hg book delete-me
232 235 $ hg book -d .
233 236 $ hg bookmark
234 237 X2 1:925d80f479bb
235 238 Y 2:db815d6d32e6
236 239 Z 0:f7b1eb17ad24
237 240 $ hg up -q Y
238 241
239 242 delete bookmark using . with no active bookmark
240 243
241 244 $ hg book delete-me
242 245 $ hg book -i delete-me
243 246 $ hg book -d .
244 247 abort: no active bookmark
245 248 [255]
246 249 $ hg up -q Y
247 250 $ hg book -d delete-me
248 251
249 252 list bookmarks
250 253
251 254 $ hg bookmark
252 255 X2 1:925d80f479bb
253 256 * Y 2:db815d6d32e6
254 257 Z 0:f7b1eb17ad24
255 258
256 259 bookmarks from a revset
257 260 $ hg bookmark -r '.^1' REVSET
258 261 $ hg bookmark -r ':tip' TIP
259 262 $ hg up -q TIP
260 263 $ hg bookmarks
261 264 REVSET 0:f7b1eb17ad24
262 265 * TIP 2:db815d6d32e6
263 266 X2 1:925d80f479bb
264 267 Y 2:db815d6d32e6
265 268 Z 0:f7b1eb17ad24
266 269
267 270 $ hg bookmark -d REVSET
268 271 $ hg bookmark -d TIP
269 272
270 273 rename without new name or multiple names
271 274
272 275 $ hg bookmark -m Y
273 276 abort: new bookmark name required
274 277 [255]
275 278 $ hg bookmark -m Y Y2 Y3
276 279 abort: only one new bookmark name allowed
277 280 [255]
278 281
279 282 delete without name
280 283
281 284 $ hg bookmark -d
282 285 abort: bookmark name required
283 286 [255]
284 287
285 288 delete nonexistent bookmark
286 289
287 290 $ hg bookmark -d A
288 291 abort: bookmark 'A' does not exist
289 292 [255]
290 293
291 294 bookmark name with spaces should be stripped
292 295
293 296 $ hg bookmark ' x y '
294 297
295 298 list bookmarks
296 299
297 300 $ hg bookmarks
298 301 X2 1:925d80f479bb
299 302 Y 2:db815d6d32e6
300 303 Z 0:f7b1eb17ad24
301 304 * x y 2:db815d6d32e6
305 $ hg log -T '{bookmarks % "{rev} {bookmark}\n"}'
306 2 Y
307 2 x y
308 1 X2
309 0 Z
302 310
303 311 look up stripped bookmark name
304 312
305 313 $ hg log -r '"x y"'
306 314 changeset: 2:db815d6d32e6
307 315 bookmark: Y
308 316 bookmark: x y
309 317 tag: tip
310 318 parent: 0:f7b1eb17ad24
311 319 user: test
312 320 date: Thu Jan 01 00:00:00 1970 +0000
313 321 summary: 2
314 322
315 323
316 324 reject bookmark name with newline
317 325
318 326 $ hg bookmark '
319 327 > '
320 328 abort: bookmark names cannot consist entirely of whitespace
321 329 [255]
322 330
323 331 $ hg bookmark -m Z '
324 332 > '
325 333 abort: bookmark names cannot consist entirely of whitespace
326 334 [255]
327 335
328 336 bookmark with reserved name
329 337
330 338 $ hg bookmark tip
331 339 abort: the name 'tip' is reserved
332 340 [255]
333 341
334 342 $ hg bookmark .
335 343 abort: the name '.' is reserved
336 344 [255]
337 345
338 346 $ hg bookmark null
339 347 abort: the name 'null' is reserved
340 348 [255]
341 349
342 350
343 351 bookmark with existing name
344 352
345 353 $ hg bookmark X2
346 354 abort: bookmark 'X2' already exists (use -f to force)
347 355 [255]
348 356
349 357 $ hg bookmark -m Y Z
350 358 abort: bookmark 'Z' already exists (use -f to force)
351 359 [255]
352 360
353 361 bookmark with name of branch
354 362
355 363 $ hg bookmark default
356 364 abort: a bookmark cannot have the name of an existing branch
357 365 [255]
358 366
359 367 $ hg bookmark -m Y default
360 368 abort: a bookmark cannot have the name of an existing branch
361 369 [255]
362 370
363 371 bookmark with integer name
364 372
365 373 $ hg bookmark 10
366 374 abort: cannot use an integer as a name
367 375 [255]
368 376
369 377 bookmark with a name that matches a node id
370 378 $ hg bookmark 925d80f479bb db815d6d32e6 --config "$TESTHOOK"
371 379 bookmark 925d80f479bb matches a changeset hash
372 380 (did you leave a -r out of an 'hg bookmark' command?)
373 381 bookmark db815d6d32e6 matches a changeset hash
374 382 (did you leave a -r out of an 'hg bookmark' command?)
375 383 test-hook-bookmark: 925d80f479bb: -> db815d6d32e69058eadefc8cffbad37675707975
376 384 test-hook-bookmark: db815d6d32e6: -> db815d6d32e69058eadefc8cffbad37675707975
377 385 $ hg bookmark -d 925d80f479bb
378 386 $ hg bookmark -d db815d6d32e6
379 387
380 388 $ cd ..
381 389
382 390 bookmark with a name that matches an ambiguous node id
383 391
384 392 $ hg init ambiguous
385 393 $ cd ambiguous
386 394 $ echo 0 > a
387 395 $ hg ci -qAm 0
388 396 $ for i in 1057 2857 4025; do
389 397 > hg up -q 0
390 398 > echo $i > a
391 399 > hg ci -qm $i
392 400 > done
393 401 $ hg up -q null
394 402 $ hg log -r0: -T '{rev}:{node}\n'
395 403 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
396 404 1:c56256a09cd28e5764f32e8e2810d0f01e2e357a
397 405 2:c5623987d205cd6d9d8389bfc40fff9dbb670b48
398 406 3:c562ddd9c94164376c20b86b0b4991636a3bf84f
399 407
400 408 $ hg bookmark -r0 c562
401 409 $ hg bookmarks
402 410 c562 0:b4e73ffab476
403 411
404 412 $ cd ..
405 413
406 414 incompatible options
407 415
408 416 $ cd repo
409 417
410 418 $ hg bookmark -m Y -d Z
411 419 abort: --delete and --rename are incompatible
412 420 [255]
413 421
414 422 $ hg bookmark -r 1 -d Z
415 423 abort: --rev is incompatible with --delete
416 424 [255]
417 425
418 426 $ hg bookmark -r 1 -m Z Y
419 427 abort: --rev is incompatible with --rename
420 428 [255]
421 429
422 430 force bookmark with existing name
423 431
424 432 $ hg bookmark -f X2 --config "$TESTHOOK"
425 433 test-hook-bookmark: X2: 925d80f479bb026b0fb3deb27503780b13f74123 -> db815d6d32e69058eadefc8cffbad37675707975
426 434
427 435 force bookmark back to where it was, should deactivate it
428 436
429 437 $ hg bookmark -fr1 X2
430 438 $ hg bookmarks
431 439 X2 1:925d80f479bb
432 440 Y 2:db815d6d32e6
433 441 Z 0:f7b1eb17ad24
434 442 x y 2:db815d6d32e6
435 443
436 444 forward bookmark to descendant without --force
437 445
438 446 $ hg bookmark Z
439 447 moving bookmark 'Z' forward from f7b1eb17ad24
440 448
441 449 list bookmarks
442 450
443 451 $ hg bookmark
444 452 X2 1:925d80f479bb
445 453 Y 2:db815d6d32e6
446 454 * Z 2:db815d6d32e6
447 455 x y 2:db815d6d32e6
456 $ hg log -T '{bookmarks % "{rev} {bookmark}\n"}'
457 2 Y
458 2 Z
459 2 x y
460 1 X2
448 461
449 462 revision but no bookmark name
450 463
451 464 $ hg bookmark -r .
452 465 abort: bookmark name required
453 466 [255]
454 467
455 468 bookmark name with whitespace only
456 469
457 470 $ hg bookmark ' '
458 471 abort: bookmark names cannot consist entirely of whitespace
459 472 [255]
460 473
461 474 $ hg bookmark -m Y ' '
462 475 abort: bookmark names cannot consist entirely of whitespace
463 476 [255]
464 477
465 478 invalid bookmark
466 479
467 480 $ hg bookmark 'foo:bar'
468 481 abort: ':' cannot be used in a name
469 482 [255]
470 483
471 484 $ hg bookmark 'foo
472 485 > bar'
473 486 abort: '\n' cannot be used in a name
474 487 [255]
475 488
476 489 the bookmark extension should be ignored now that it is part of core
477 490
478 491 $ echo "[extensions]" >> $HGRCPATH
479 492 $ echo "bookmarks=" >> $HGRCPATH
480 493 $ hg bookmarks
481 494 X2 1:925d80f479bb
482 495 Y 2:db815d6d32e6
483 496 * Z 2:db815d6d32e6
484 497 x y 2:db815d6d32e6
485 498
486 499 test summary
487 500
488 501 $ hg summary
489 502 parent: 2:db815d6d32e6 tip
490 503 2
491 504 branch: default
492 505 bookmarks: *Z Y x y
493 506 commit: (clean)
494 507 update: 1 new changesets, 2 branch heads (merge)
495 508 phases: 3 draft
496 509
497 510 test id
498 511
499 512 $ hg id
500 513 db815d6d32e6 tip Y/Z/x y
501 514
502 515 test rollback
503 516
504 517 $ echo foo > f1
505 518 $ hg bookmark tmp-rollback
506 519 $ hg ci -Amr
507 520 adding f1
508 521 $ hg bookmarks
509 522 X2 1:925d80f479bb
510 523 Y 2:db815d6d32e6
511 524 Z 2:db815d6d32e6
512 525 * tmp-rollback 3:2bf5cfec5864
513 526 x y 2:db815d6d32e6
514 527 $ hg rollback
515 528 repository tip rolled back to revision 2 (undo commit)
516 529 working directory now based on revision 2
517 530 $ hg bookmarks
518 531 X2 1:925d80f479bb
519 532 Y 2:db815d6d32e6
520 533 Z 2:db815d6d32e6
521 534 * tmp-rollback 2:db815d6d32e6
522 535 x y 2:db815d6d32e6
523 536 $ hg bookmark -f Z -r 1
524 537 $ hg rollback
525 538 repository tip rolled back to revision 2 (undo bookmark)
526 539 $ hg bookmarks
527 540 X2 1:925d80f479bb
528 541 Y 2:db815d6d32e6
529 542 Z 2:db815d6d32e6
530 543 * tmp-rollback 2:db815d6d32e6
531 544 x y 2:db815d6d32e6
532 545 $ hg bookmark -d tmp-rollback
533 546
534 547 activate bookmark on working dir parent without --force
535 548
536 549 $ hg bookmark --inactive Z
537 550 $ hg bookmark Z
538 551
539 552 test clone
540 553
541 554 $ hg bookmark -r 2 -i @
542 555 $ hg bookmark -r 2 -i a@
543 556 $ hg bookmarks
544 557 @ 2:db815d6d32e6
545 558 X2 1:925d80f479bb
546 559 Y 2:db815d6d32e6
547 560 * Z 2:db815d6d32e6
548 561 a@ 2:db815d6d32e6
549 562 x y 2:db815d6d32e6
550 563 $ hg clone . cloned-bookmarks
551 564 updating to bookmark @
552 565 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
553 566 $ hg -R cloned-bookmarks bookmarks
554 567 * @ 2:db815d6d32e6
555 568 X2 1:925d80f479bb
556 569 Y 2:db815d6d32e6
557 570 Z 2:db815d6d32e6
558 571 a@ 2:db815d6d32e6
559 572 x y 2:db815d6d32e6
560 573
561 574 test clone with pull protocol
562 575
563 576 $ hg clone --pull . cloned-bookmarks-pull
564 577 requesting all changes
565 578 adding changesets
566 579 adding manifests
567 580 adding file changes
568 581 added 3 changesets with 3 changes to 3 files (+1 heads)
569 582 new changesets f7b1eb17ad24:db815d6d32e6
570 583 updating to bookmark @
571 584 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
572 585 $ hg -R cloned-bookmarks-pull bookmarks
573 586 * @ 2:db815d6d32e6
574 587 X2 1:925d80f479bb
575 588 Y 2:db815d6d32e6
576 589 Z 2:db815d6d32e6
577 590 a@ 2:db815d6d32e6
578 591 x y 2:db815d6d32e6
579 592
580 593 delete multiple bookmarks at once
581 594
582 595 $ hg bookmark -d @ a@
583 596
584 597 test clone with a bookmark named "default" (issue3677)
585 598
586 599 $ hg bookmark -r 1 -f -i default
587 600 $ hg clone . cloned-bookmark-default
588 601 updating to branch default
589 602 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
590 603 $ hg -R cloned-bookmark-default bookmarks
591 604 X2 1:925d80f479bb
592 605 Y 2:db815d6d32e6
593 606 Z 2:db815d6d32e6
594 607 default 1:925d80f479bb
595 608 x y 2:db815d6d32e6
596 609 $ hg -R cloned-bookmark-default parents -q
597 610 2:db815d6d32e6
598 611 $ hg bookmark -d default
599 612
600 613 test clone with a specific revision
601 614
602 615 $ hg clone -r 925d80 . cloned-bookmarks-rev
603 616 adding changesets
604 617 adding manifests
605 618 adding file changes
606 619 added 2 changesets with 2 changes to 2 files
607 620 new changesets f7b1eb17ad24:925d80f479bb
608 621 updating to branch default
609 622 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
610 623 $ hg -R cloned-bookmarks-rev bookmarks
611 624 X2 1:925d80f479bb
612 625
613 626 test clone with update to a bookmark
614 627
615 628 $ hg clone -u Z . ../cloned-bookmarks-update
616 629 updating to branch default
617 630 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
618 631 $ hg -R ../cloned-bookmarks-update bookmarks
619 632 X2 1:925d80f479bb
620 633 Y 2:db815d6d32e6
621 634 * Z 2:db815d6d32e6
622 635 x y 2:db815d6d32e6
623 636
624 637 create bundle with two heads
625 638
626 639 $ hg clone . tobundle
627 640 updating to branch default
628 641 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
629 642 $ echo x > tobundle/x
630 643 $ hg -R tobundle add tobundle/x
631 644 $ hg -R tobundle commit -m'x'
632 645 $ hg -R tobundle update -r -2
633 646 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
634 647 $ echo y > tobundle/y
635 648 $ hg -R tobundle branch test
636 649 marked working directory as branch test
637 650 (branches are permanent and global, did you want a bookmark?)
638 651 $ hg -R tobundle add tobundle/y
639 652 $ hg -R tobundle commit -m'y'
640 653 $ hg -R tobundle bundle tobundle.hg
641 654 searching for changes
642 655 2 changesets found
643 656 $ hg unbundle tobundle.hg
644 657 adding changesets
645 658 adding manifests
646 659 adding file changes
647 660 added 2 changesets with 2 changes to 2 files (+1 heads)
648 661 new changesets 125c9a1d6df6:9ba5f110a0b3
649 662 (run 'hg heads' to see heads, 'hg merge' to merge)
650 663
651 664 update to active bookmark if it's not the parent
652 665
653 666 (it is known issue that fsmonitor can't handle nested repositories. In
654 667 this test scenario, cloned-bookmark-default and tobundle exist in the
655 668 working directory of current repository)
656 669
657 670 $ hg summary
658 671 parent: 2:db815d6d32e6
659 672 2
660 673 branch: default
661 674 bookmarks: *Z Y x y
662 675 commit: 1 added, 1 unknown (new branch head) (no-fsmonitor !)
663 676 commit: 1 added, * unknown (new branch head) (glob) (fsmonitor !)
664 677 update: 2 new changesets (update)
665 678 phases: 5 draft
666 679 $ hg update
667 680 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
668 681 updating bookmark Z
669 682 $ hg bookmarks
670 683 X2 1:925d80f479bb
671 684 Y 2:db815d6d32e6
672 685 * Z 3:125c9a1d6df6
673 686 x y 2:db815d6d32e6
674 687
675 688 pull --update works the same as pull && update
676 689
677 690 $ hg bookmark -r3 Y
678 691 moving bookmark 'Y' forward from db815d6d32e6
679 692 $ cp -R ../cloned-bookmarks-update ../cloned-bookmarks-manual-update
680 693 $ cp -R ../cloned-bookmarks-update ../cloned-bookmarks-manual-update-with-divergence
681 694
682 695 (manual version)
683 696
684 697 $ hg -R ../cloned-bookmarks-manual-update update Y
685 698 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
686 699 (activating bookmark Y)
687 700 $ hg -R ../cloned-bookmarks-manual-update pull .
688 701 pulling from .
689 702 searching for changes
690 703 adding changesets
691 704 adding manifests
692 705 adding file changes
693 706 added 2 changesets with 2 changes to 2 files (+1 heads)
694 707 updating bookmark Y
695 708 updating bookmark Z
696 709 new changesets 125c9a1d6df6:9ba5f110a0b3
697 710 (run 'hg heads' to see heads, 'hg merge' to merge)
698 711
699 712 (# tests strange but with --date crashing when bookmark have to move)
700 713
701 714 $ hg -R ../cloned-bookmarks-manual-update update -d 1986
702 715 abort: revision matching date not found
703 716 [255]
704 717 $ hg -R ../cloned-bookmarks-manual-update update
705 718 updating to active bookmark Y
706 719 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
707 720
708 721 (all in one version)
709 722
710 723 $ hg -R ../cloned-bookmarks-update update Y
711 724 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
712 725 (activating bookmark Y)
713 726 $ hg -R ../cloned-bookmarks-update pull --update .
714 727 pulling from .
715 728 searching for changes
716 729 adding changesets
717 730 adding manifests
718 731 adding file changes
719 732 added 2 changesets with 2 changes to 2 files (+1 heads)
720 733 updating bookmark Y
721 734 updating bookmark Z
722 735 new changesets 125c9a1d6df6:9ba5f110a0b3
723 736 updating to active bookmark Y
724 737 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
725 738
726 739 We warn about divergent during bare update to the active bookmark
727 740
728 741 $ hg -R ../cloned-bookmarks-manual-update-with-divergence update Y
729 742 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
730 743 (activating bookmark Y)
731 744 $ hg -R ../cloned-bookmarks-manual-update-with-divergence bookmarks -r X2 Y@1
732 745 $ hg -R ../cloned-bookmarks-manual-update-with-divergence bookmarks
733 746 X2 1:925d80f479bb
734 747 * Y 2:db815d6d32e6
735 748 Y@1 1:925d80f479bb
736 749 Z 2:db815d6d32e6
737 750 x y 2:db815d6d32e6
738 751 $ hg -R ../cloned-bookmarks-manual-update-with-divergence pull
739 752 pulling from $TESTTMP/repo
740 753 searching for changes
741 754 adding changesets
742 755 adding manifests
743 756 adding file changes
744 757 added 2 changesets with 2 changes to 2 files (+1 heads)
745 758 updating bookmark Y
746 759 updating bookmark Z
747 760 new changesets 125c9a1d6df6:9ba5f110a0b3
748 761 (run 'hg heads' to see heads, 'hg merge' to merge)
749 762 $ hg -R ../cloned-bookmarks-manual-update-with-divergence update
750 763 updating to active bookmark Y
751 764 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
752 765 1 other divergent bookmarks for "Y"
753 766
754 767 test wrongly formated bookmark
755 768
756 769 $ echo '' >> .hg/bookmarks
757 770 $ hg bookmarks
758 771 X2 1:925d80f479bb
759 772 Y 3:125c9a1d6df6
760 773 * Z 3:125c9a1d6df6
761 774 x y 2:db815d6d32e6
762 775 $ echo "Ican'thasformatedlines" >> .hg/bookmarks
763 776 $ hg bookmarks
764 777 malformed line in .hg/bookmarks: "Ican'thasformatedlines"
765 778 X2 1:925d80f479bb
766 779 Y 3:125c9a1d6df6
767 780 * Z 3:125c9a1d6df6
768 781 x y 2:db815d6d32e6
769 782
770 783 test missing revisions
771 784
772 785 $ echo "925d80f479b925d80f479bc925d80f479bccabab z" > .hg/bookmarks
773 786 $ hg book
774 787 no bookmarks set
775 788
776 789 test stripping a non-checked-out but bookmarked revision
777 790
778 791 $ hg log --graph
779 792 o changeset: 4:9ba5f110a0b3
780 793 | branch: test
781 794 | tag: tip
782 795 | parent: 2:db815d6d32e6
783 796 | user: test
784 797 | date: Thu Jan 01 00:00:00 1970 +0000
785 798 | summary: y
786 799 |
787 800 | @ changeset: 3:125c9a1d6df6
788 801 |/ user: test
789 802 | date: Thu Jan 01 00:00:00 1970 +0000
790 803 | summary: x
791 804 |
792 805 o changeset: 2:db815d6d32e6
793 806 | parent: 0:f7b1eb17ad24
794 807 | user: test
795 808 | date: Thu Jan 01 00:00:00 1970 +0000
796 809 | summary: 2
797 810 |
798 811 | o changeset: 1:925d80f479bb
799 812 |/ user: test
800 813 | date: Thu Jan 01 00:00:00 1970 +0000
801 814 | summary: 1
802 815 |
803 816 o changeset: 0:f7b1eb17ad24
804 817 user: test
805 818 date: Thu Jan 01 00:00:00 1970 +0000
806 819 summary: 0
807 820
808 821 $ hg book should-end-on-two
809 822 $ hg co --clean 4
810 823 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
811 824 (leaving bookmark should-end-on-two)
812 825 $ hg book four
813 826 $ hg --config extensions.mq= strip 3
814 827 saved backup bundle to * (glob)
815 828 should-end-on-two should end up pointing to revision 2, as that's the
816 829 tipmost surviving ancestor of the stripped revision.
817 830 $ hg log --graph
818 831 @ changeset: 3:9ba5f110a0b3
819 832 | branch: test
820 833 | bookmark: four
821 834 | tag: tip
822 835 | user: test
823 836 | date: Thu Jan 01 00:00:00 1970 +0000
824 837 | summary: y
825 838 |
826 839 o changeset: 2:db815d6d32e6
827 840 | bookmark: should-end-on-two
828 841 | parent: 0:f7b1eb17ad24
829 842 | user: test
830 843 | date: Thu Jan 01 00:00:00 1970 +0000
831 844 | summary: 2
832 845 |
833 846 | o changeset: 1:925d80f479bb
834 847 |/ user: test
835 848 | date: Thu Jan 01 00:00:00 1970 +0000
836 849 | summary: 1
837 850 |
838 851 o changeset: 0:f7b1eb17ad24
839 852 user: test
840 853 date: Thu Jan 01 00:00:00 1970 +0000
841 854 summary: 0
842 855
843 856
844 857 no-op update doesn't deactivate bookmarks
845 858
846 859 (it is known issue that fsmonitor can't handle nested repositories. In
847 860 this test scenario, cloned-bookmark-default and tobundle exist in the
848 861 working directory of current repository)
849 862
850 863 $ hg bookmarks
851 864 * four 3:9ba5f110a0b3
852 865 should-end-on-two 2:db815d6d32e6
853 866 $ hg up four
854 867 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
855 868 $ hg up
856 869 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
857 870 $ hg sum
858 871 parent: 3:9ba5f110a0b3 tip
859 872 y
860 873 branch: test
861 874 bookmarks: *four
862 875 commit: 2 unknown (clean) (no-fsmonitor !)
863 876 commit: * unknown (clean) (glob) (fsmonitor !)
864 877 update: (current)
865 878 phases: 4 draft
866 879
867 880 test clearing divergent bookmarks of linear ancestors
868 881
869 882 $ hg bookmark Z -r 0
870 883 $ hg bookmark Z@1 -r 1
871 884 $ hg bookmark Z@2 -r 2
872 885 $ hg bookmark Z@3 -r 3
873 886 $ hg book
874 887 Z 0:f7b1eb17ad24
875 888 Z@1 1:925d80f479bb
876 889 Z@2 2:db815d6d32e6
877 890 Z@3 3:9ba5f110a0b3
878 891 * four 3:9ba5f110a0b3
879 892 should-end-on-two 2:db815d6d32e6
880 893 $ hg bookmark Z
881 894 moving bookmark 'Z' forward from f7b1eb17ad24
882 895 $ hg book
883 896 * Z 3:9ba5f110a0b3
884 897 Z@1 1:925d80f479bb
885 898 four 3:9ba5f110a0b3
886 899 should-end-on-two 2:db815d6d32e6
887 900
888 901 test clearing only a single divergent bookmark across branches
889 902
890 903 $ hg book foo -r 1
891 904 $ hg book foo@1 -r 0
892 905 $ hg book foo@2 -r 2
893 906 $ hg book foo@3 -r 3
894 907 $ hg book foo -r foo@3
895 908 $ hg book
896 909 * Z 3:9ba5f110a0b3
897 910 Z@1 1:925d80f479bb
898 911 foo 3:9ba5f110a0b3
899 912 foo@1 0:f7b1eb17ad24
900 913 foo@2 2:db815d6d32e6
901 914 four 3:9ba5f110a0b3
902 915 should-end-on-two 2:db815d6d32e6
903 916
904 917 pull --update works the same as pull && update (case #2)
905 918
906 919 It is assumed that "hg pull" itself doesn't update current active
907 920 bookmark ('Y' in tests below).
908 921
909 922 $ hg pull -q ../cloned-bookmarks-update
910 923 divergent bookmark Z stored as Z@2
911 924
912 925 (pulling revision on another named branch with --update updates
913 926 neither the working directory nor current active bookmark: "no-op"
914 927 case)
915 928
916 929 $ echo yy >> y
917 930 $ hg commit -m yy
918 931
919 932 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
920 933 * Y 3:125c9a1d6df6
921 934 $ hg -R ../cloned-bookmarks-update pull . --update
922 935 pulling from .
923 936 searching for changes
924 937 adding changesets
925 938 adding manifests
926 939 adding file changes
927 940 added 1 changesets with 1 changes to 1 files
928 941 divergent bookmark Z stored as Z@default
929 942 adding remote bookmark foo
930 943 adding remote bookmark four
931 944 adding remote bookmark should-end-on-two
932 945 new changesets 5fb12f0f2d51
933 946 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
934 947 $ hg -R ../cloned-bookmarks-update parents -T "{rev}:{node|short}\n"
935 948 3:125c9a1d6df6
936 949 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
937 950 * Y 3:125c9a1d6df6
938 951
939 952 (pulling revision on current named/topological branch with --update
940 953 updates the working directory and current active bookmark)
941 954
942 955 $ hg update -C -q 125c9a1d6df6
943 956 $ echo xx >> x
944 957 $ hg commit -m xx
945 958
946 959 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
947 960 * Y 3:125c9a1d6df6
948 961 $ hg -R ../cloned-bookmarks-update pull . --update
949 962 pulling from .
950 963 searching for changes
951 964 adding changesets
952 965 adding manifests
953 966 adding file changes
954 967 added 1 changesets with 1 changes to 1 files
955 968 divergent bookmark Z stored as Z@default
956 969 new changesets 81dcce76aa0b
957 970 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
958 971 updating bookmark Y
959 972 $ hg -R ../cloned-bookmarks-update parents -T "{rev}:{node|short}\n"
960 973 6:81dcce76aa0b
961 974 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
962 975 * Y 6:81dcce76aa0b
963 976
964 977 $ cd ..
965 978
966 979 ensure changelog is written before bookmarks
967 980 $ hg init orderrepo
968 981 $ cd orderrepo
969 982 $ touch a
970 983 $ hg commit -Aqm one
971 984 $ hg book mybook
972 985 $ echo a > a
973 986
974 987 $ cat > $TESTTMP/pausefinalize.py <<EOF
975 988 > from __future__ import absolute_import
976 989 > import os
977 990 > import time
978 991 > from mercurial import extensions, localrepo
979 992 > def transaction(orig, self, desc, report=None):
980 993 > tr = orig(self, desc, report)
981 994 > def sleep(*args, **kwargs):
982 995 > retry = 20
983 996 > while retry > 0 and not os.path.exists(b"$TESTTMP/unpause"):
984 997 > retry -= 1
985 998 > time.sleep(0.5)
986 999 > if os.path.exists(b"$TESTTMP/unpause"):
987 1000 > os.remove(b"$TESTTMP/unpause")
988 1001 > # It is important that this finalizer start with 'a', so it runs before
989 1002 > # the changelog finalizer appends to the changelog.
990 1003 > tr.addfinalize(b'a-sleep', sleep)
991 1004 > return tr
992 1005 >
993 1006 > def extsetup(ui):
994 1007 > # This extension inserts an artifical pause during the transaction
995 1008 > # finalizer, so we can run commands mid-transaction-close.
996 1009 > extensions.wrapfunction(localrepo.localrepository, 'transaction',
997 1010 > transaction)
998 1011 > EOF
999 1012 $ hg commit -qm two --config extensions.pausefinalize=$TESTTMP/pausefinalize.py &
1000 1013 $ sleep 2
1001 1014 $ hg log -r .
1002 1015 changeset: 0:867bc5792c8c
1003 1016 bookmark: mybook
1004 1017 tag: tip
1005 1018 user: test
1006 1019 date: Thu Jan 01 00:00:00 1970 +0000
1007 1020 summary: one
1008 1021
1009 1022 $ hg bookmarks
1010 1023 * mybook 0:867bc5792c8c
1011 1024 $ touch $TESTTMP/unpause
1012 1025
1013 1026 $ cd ..
1014 1027
1015 1028 check whether HG_PENDING makes pending changes only in related
1016 1029 repositories visible to an external hook.
1017 1030
1018 1031 (emulate a transaction running concurrently by copied
1019 1032 .hg/bookmarks.pending in subsequent test)
1020 1033
1021 1034 $ cat > $TESTTMP/savepending.sh <<EOF
1022 1035 > cp .hg/bookmarks.pending .hg/bookmarks.pending.saved
1023 1036 > exit 1 # to avoid adding new bookmark for subsequent tests
1024 1037 > EOF
1025 1038
1026 1039 $ hg init unrelated
1027 1040 $ cd unrelated
1028 1041 $ echo a > a
1029 1042 $ hg add a
1030 1043 $ hg commit -m '#0'
1031 1044 $ hg --config hooks.pretxnclose="sh $TESTTMP/savepending.sh" bookmarks INVISIBLE
1032 1045 transaction abort!
1033 1046 rollback completed
1034 1047 abort: pretxnclose hook exited with status 1
1035 1048 [255]
1036 1049 $ cp .hg/bookmarks.pending.saved .hg/bookmarks.pending
1037 1050
1038 1051 (check visible bookmarks while transaction running in repo)
1039 1052
1040 1053 $ cat > $TESTTMP/checkpending.sh <<EOF
1041 1054 > echo "@repo"
1042 1055 > hg -R "$TESTTMP/repo" bookmarks
1043 1056 > echo "@unrelated"
1044 1057 > hg -R "$TESTTMP/unrelated" bookmarks
1045 1058 > exit 1 # to avoid adding new bookmark for subsequent tests
1046 1059 > EOF
1047 1060
1048 1061 $ cd ../repo
1049 1062 $ hg --config hooks.pretxnclose="sh $TESTTMP/checkpending.sh" bookmarks NEW
1050 1063 @repo
1051 1064 * NEW 6:81dcce76aa0b
1052 1065 X2 1:925d80f479bb
1053 1066 Y 4:125c9a1d6df6
1054 1067 Z 5:5fb12f0f2d51
1055 1068 Z@1 1:925d80f479bb
1056 1069 Z@2 4:125c9a1d6df6
1057 1070 foo 3:9ba5f110a0b3
1058 1071 foo@1 0:f7b1eb17ad24
1059 1072 foo@2 2:db815d6d32e6
1060 1073 four 3:9ba5f110a0b3
1061 1074 should-end-on-two 2:db815d6d32e6
1062 1075 x y 2:db815d6d32e6
1063 1076 @unrelated
1064 1077 no bookmarks set
1065 1078 transaction abort!
1066 1079 rollback completed
1067 1080 abort: pretxnclose hook exited with status 1
1068 1081 [255]
1069 1082
1070 1083 Check pretxnclose-bookmark can abort a transaction
1071 1084 --------------------------------------------------
1072 1085
1073 1086 add hooks:
1074 1087
1075 1088 * to prevent NEW bookmark on a non-public changeset
1076 1089 * to prevent non-forward move of NEW bookmark
1077 1090
1078 1091 $ cat << EOF >> .hg/hgrc
1079 1092 > [hooks]
1080 1093 > pretxnclose-bookmark.force-public = sh -c "(echo \$HG_BOOKMARK| grep -v NEW > /dev/null) || [ -z \"\$HG_NODE\" ] || (hg log -r \"\$HG_NODE\" -T '{phase}' | grep public > /dev/null)"
1081 1094 > pretxnclose-bookmark.force-forward = sh -c "(echo \$HG_BOOKMARK| grep -v NEW > /dev/null) || [ -z \"\$HG_NODE\" ] || (hg log -r \"max(\$HG_OLDNODE::\$HG_NODE)\" -T 'MATCH' | grep MATCH > /dev/null)"
1082 1095 > EOF
1083 1096
1084 1097 $ hg log -G -T phases
1085 1098 @ changeset: 6:81dcce76aa0b
1086 1099 | tag: tip
1087 1100 | phase: draft
1088 1101 | parent: 4:125c9a1d6df6
1089 1102 | user: test
1090 1103 | date: Thu Jan 01 00:00:00 1970 +0000
1091 1104 | summary: xx
1092 1105 |
1093 1106 | o changeset: 5:5fb12f0f2d51
1094 1107 | | branch: test
1095 1108 | | bookmark: Z
1096 1109 | | phase: draft
1097 1110 | | parent: 3:9ba5f110a0b3
1098 1111 | | user: test
1099 1112 | | date: Thu Jan 01 00:00:00 1970 +0000
1100 1113 | | summary: yy
1101 1114 | |
1102 1115 o | changeset: 4:125c9a1d6df6
1103 1116 | | bookmark: Y
1104 1117 | | bookmark: Z@2
1105 1118 | | phase: public
1106 1119 | | parent: 2:db815d6d32e6
1107 1120 | | user: test
1108 1121 | | date: Thu Jan 01 00:00:00 1970 +0000
1109 1122 | | summary: x
1110 1123 | |
1111 1124 | o changeset: 3:9ba5f110a0b3
1112 1125 |/ branch: test
1113 1126 | bookmark: foo
1114 1127 | bookmark: four
1115 1128 | phase: public
1116 1129 | user: test
1117 1130 | date: Thu Jan 01 00:00:00 1970 +0000
1118 1131 | summary: y
1119 1132 |
1120 1133 o changeset: 2:db815d6d32e6
1121 1134 | bookmark: foo@2
1122 1135 | bookmark: should-end-on-two
1123 1136 | bookmark: x y
1124 1137 | phase: public
1125 1138 | parent: 0:f7b1eb17ad24
1126 1139 | user: test
1127 1140 | date: Thu Jan 01 00:00:00 1970 +0000
1128 1141 | summary: 2
1129 1142 |
1130 1143 | o changeset: 1:925d80f479bb
1131 1144 |/ bookmark: X2
1132 1145 | bookmark: Z@1
1133 1146 | phase: public
1134 1147 | user: test
1135 1148 | date: Thu Jan 01 00:00:00 1970 +0000
1136 1149 | summary: 1
1137 1150 |
1138 1151 o changeset: 0:f7b1eb17ad24
1139 1152 bookmark: foo@1
1140 1153 phase: public
1141 1154 user: test
1142 1155 date: Thu Jan 01 00:00:00 1970 +0000
1143 1156 summary: 0
1144 1157
1145 1158
1146 1159 attempt to create on a default changeset
1147 1160
1148 1161 $ hg bookmark -r 81dcce76aa0b NEW
1149 1162 transaction abort!
1150 1163 rollback completed
1151 1164 abort: pretxnclose-bookmark.force-public hook exited with status 1
1152 1165 [255]
1153 1166
1154 1167 create on a public changeset
1155 1168
1156 1169 $ hg bookmark -r 9ba5f110a0b3 NEW
1157 1170
1158 1171 move to the other branch
1159 1172
1160 1173 $ hg bookmark -f -r 125c9a1d6df6 NEW
1161 1174 transaction abort!
1162 1175 rollback completed
1163 1176 abort: pretxnclose-bookmark.force-forward hook exited with status 1
1164 1177 [255]
General Comments 0
You need to be logged in to leave comments. Login now