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