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