##// END OF EJS Templates
bookmarks: factor method _printer out of for loop in printbookmarks...
Sean Farley -
r33011:8299eb9b default
parent child Browse files
Show More
@@ -1,794 +1,805
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
12 12 from .i18n import _
13 13 from .node import (
14 14 bin,
15 15 hex,
16 16 short,
17 17 )
18 18 from . import (
19 19 encoding,
20 20 error,
21 21 lock as lockmod,
22 22 obsolete,
23 23 scmutil,
24 24 txnutil,
25 25 util,
26 26 )
27 27
28 28 # label constants
29 29 # until 3.5, bookmarks.current was the advertised name, not
30 30 # bookmarks.active, so we must use both to avoid breaking old
31 31 # custom styles
32 32 activebookmarklabel = 'bookmarks.active bookmarks.current'
33 33
34 34 def _getbkfile(repo):
35 35 """Hook so that extensions that mess with the store can hook bm storage.
36 36
37 37 For core, this just handles wether we should see pending
38 38 bookmarks or the committed ones. Other extensions (like share)
39 39 may need to tweak this behavior further.
40 40 """
41 41 fp, pending = txnutil.trypending(repo.root, repo.vfs, 'bookmarks')
42 42 return fp
43 43
44 44 class bmstore(dict):
45 45 """Storage for bookmarks.
46 46
47 47 This object should do all bookmark-related reads and writes, so
48 48 that it's fairly simple to replace the storage underlying
49 49 bookmarks without having to clone the logic surrounding
50 50 bookmarks. This type also should manage the active bookmark, if
51 51 any.
52 52
53 53 This particular bmstore implementation stores bookmarks as
54 54 {hash}\s{name}\n (the same format as localtags) in
55 55 .hg/bookmarks. The mapping is stored as {name: nodeid}.
56 56 """
57 57
58 58 def __init__(self, repo):
59 59 dict.__init__(self)
60 60 self._repo = repo
61 61 self._clean = True
62 62 self._aclean = True
63 63 nm = repo.changelog.nodemap
64 64 tonode = bin # force local lookup
65 65 setitem = dict.__setitem__
66 66 try:
67 67 with _getbkfile(repo) as bkfile:
68 68 for line in bkfile:
69 69 line = line.strip()
70 70 if not line:
71 71 continue
72 72 try:
73 73 sha, refspec = line.split(' ', 1)
74 74 node = tonode(sha)
75 75 if node in nm:
76 76 refspec = encoding.tolocal(refspec)
77 77 setitem(self, refspec, node)
78 78 except (TypeError, ValueError):
79 79 # TypeError:
80 80 # - bin(...)
81 81 # ValueError:
82 82 # - node in nm, for non-20-bytes entry
83 83 # - split(...), for string without ' '
84 84 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
85 85 % line)
86 86 except IOError as inst:
87 87 if inst.errno != errno.ENOENT:
88 88 raise
89 89 self._active = _readactive(repo, self)
90 90
91 91 @property
92 92 def active(self):
93 93 return self._active
94 94
95 95 @active.setter
96 96 def active(self, mark):
97 97 if mark is not None and mark not in self:
98 98 raise AssertionError('bookmark %s does not exist!' % mark)
99 99
100 100 self._active = mark
101 101 self._aclean = False
102 102
103 103 def __setitem__(self, *args, **kwargs):
104 104 self._clean = False
105 105 return dict.__setitem__(self, *args, **kwargs)
106 106
107 107 def __delitem__(self, key):
108 108 self._clean = False
109 109 return dict.__delitem__(self, key)
110 110
111 111 def recordchange(self, tr):
112 112 """record that bookmarks have been changed in a transaction
113 113
114 114 The transaction is then responsible for updating the file content."""
115 115 tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
116 116 location='plain')
117 117 tr.hookargs['bookmark_moved'] = '1'
118 118
119 119 def _writerepo(self, repo):
120 120 """Factored out for extensibility"""
121 121 rbm = repo._bookmarks
122 122 if rbm.active not in self:
123 123 rbm.active = None
124 124 rbm._writeactive()
125 125
126 126 with repo.wlock():
127 127 file_ = repo.vfs('bookmarks', 'w', atomictemp=True,
128 128 checkambig=True)
129 129 try:
130 130 self._write(file_)
131 131 except: # re-raises
132 132 file_.discard()
133 133 raise
134 134 finally:
135 135 file_.close()
136 136
137 137 def _writeactive(self):
138 138 if self._aclean:
139 139 return
140 140 with self._repo.wlock():
141 141 if self._active is not None:
142 142 f = self._repo.vfs('bookmarks.current', 'w', atomictemp=True,
143 143 checkambig=True)
144 144 try:
145 145 f.write(encoding.fromlocal(self._active))
146 146 finally:
147 147 f.close()
148 148 else:
149 149 self._repo.vfs.tryunlink('bookmarks.current')
150 150 self._aclean = True
151 151
152 152 def _write(self, fp):
153 153 for name, node in self.iteritems():
154 154 fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
155 155 self._clean = True
156 156 self._repo.invalidatevolatilesets()
157 157
158 158 def expandname(self, bname):
159 159 if bname == '.':
160 160 if self.active:
161 161 return self.active
162 162 else:
163 163 raise error.Abort(_("no active bookmark"))
164 164 return bname
165 165
166 166 def checkconflict(self, mark, force=False, target=None):
167 167 """check repo for a potential clash of mark with an existing bookmark,
168 168 branch, or hash
169 169
170 170 If target is supplied, then check that we are moving the bookmark
171 171 forward.
172 172
173 173 If force is supplied, then forcibly move the bookmark to a new commit
174 174 regardless if it is a move forward.
175 175 """
176 176 cur = self._repo.changectx('.').node()
177 177 if mark in self and not force:
178 178 if target:
179 179 if self[mark] == target and target == cur:
180 180 # re-activating a bookmark
181 181 return
182 182 rev = self._repo[target].rev()
183 183 anc = self._repo.changelog.ancestors([rev])
184 184 bmctx = self._repo[self[mark]]
185 185 divs = [self._repo[b].node() for b in self
186 186 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
187 187
188 188 # allow resolving a single divergent bookmark even if moving
189 189 # the bookmark across branches when a revision is specified
190 190 # that contains a divergent bookmark
191 191 if bmctx.rev() not in anc and target in divs:
192 192 deletedivergent(self._repo, [target], mark)
193 193 return
194 194
195 195 deletefrom = [b for b in divs
196 196 if self._repo[b].rev() in anc or b == target]
197 197 deletedivergent(self._repo, deletefrom, mark)
198 198 if validdest(self._repo, bmctx, self._repo[target]):
199 199 self._repo.ui.status(
200 200 _("moving bookmark '%s' forward from %s\n") %
201 201 (mark, short(bmctx.node())))
202 202 return
203 203 raise error.Abort(_("bookmark '%s' already exists "
204 204 "(use -f to force)") % mark)
205 205 if ((mark in self._repo.branchmap() or
206 206 mark == self._repo.dirstate.branch()) and not force):
207 207 raise error.Abort(
208 208 _("a bookmark cannot have the name of an existing branch"))
209 209 if len(mark) > 3 and not force:
210 210 try:
211 211 shadowhash = (mark in self._repo)
212 212 except error.LookupError: # ambiguous identifier
213 213 shadowhash = False
214 214 if shadowhash:
215 215 self._repo.ui.warn(
216 216 _("bookmark %s matches a changeset hash\n"
217 217 "(did you leave a -r out of an 'hg bookmark' "
218 218 "command?)\n")
219 219 % mark)
220 220
221 221 def _readactive(repo, marks):
222 222 """
223 223 Get the active bookmark. We can have an active bookmark that updates
224 224 itself as we commit. This function returns the name of that bookmark.
225 225 It is stored in .hg/bookmarks.current
226 226 """
227 227 mark = None
228 228 try:
229 229 file = repo.vfs('bookmarks.current')
230 230 except IOError as inst:
231 231 if inst.errno != errno.ENOENT:
232 232 raise
233 233 return None
234 234 try:
235 235 # No readline() in osutil.posixfile, reading everything is
236 236 # cheap.
237 237 # Note that it's possible for readlines() here to raise
238 238 # IOError, since we might be reading the active mark over
239 239 # static-http which only tries to load the file when we try
240 240 # to read from it.
241 241 mark = encoding.tolocal((file.readlines() or [''])[0])
242 242 if mark == '' or mark not in marks:
243 243 mark = None
244 244 except IOError as inst:
245 245 if inst.errno != errno.ENOENT:
246 246 raise
247 247 return None
248 248 finally:
249 249 file.close()
250 250 return mark
251 251
252 252 def activate(repo, mark):
253 253 """
254 254 Set the given bookmark to be 'active', meaning that this bookmark will
255 255 follow new commits that are made.
256 256 The name is recorded in .hg/bookmarks.current
257 257 """
258 258 repo._bookmarks.active = mark
259 259 repo._bookmarks._writeactive()
260 260
261 261 def deactivate(repo):
262 262 """
263 263 Unset the active bookmark in this repository.
264 264 """
265 265 repo._bookmarks.active = None
266 266 repo._bookmarks._writeactive()
267 267
268 268 def isactivewdirparent(repo):
269 269 """
270 270 Tell whether the 'active' bookmark (the one that follows new commits)
271 271 points to one of the parents of the current working directory (wdir).
272 272
273 273 While this is normally the case, it can on occasion be false; for example,
274 274 immediately after a pull, the active bookmark can be moved to point
275 275 to a place different than the wdir. This is solved by running `hg update`.
276 276 """
277 277 mark = repo._activebookmark
278 278 marks = repo._bookmarks
279 279 parents = [p.node() for p in repo[None].parents()]
280 280 return (mark in marks and marks[mark] in parents)
281 281
282 282 def deletedivergent(repo, deletefrom, bm):
283 283 '''Delete divergent versions of bm on nodes in deletefrom.
284 284
285 285 Return True if at least one bookmark was deleted, False otherwise.'''
286 286 deleted = False
287 287 marks = repo._bookmarks
288 288 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
289 289 for mark in divergent:
290 290 if mark == '@' or '@' not in mark:
291 291 # can't be divergent by definition
292 292 continue
293 293 if mark and marks[mark] in deletefrom:
294 294 if mark != bm:
295 295 del marks[mark]
296 296 deleted = True
297 297 return deleted
298 298
299 299 def headsforactive(repo):
300 300 """Given a repo with an active bookmark, return divergent bookmark nodes.
301 301
302 302 Args:
303 303 repo: A repository with an active bookmark.
304 304
305 305 Returns:
306 306 A list of binary node ids that is the full list of other
307 307 revisions with bookmarks divergent from the active bookmark. If
308 308 there were no divergent bookmarks, then this list will contain
309 309 only one entry.
310 310 """
311 311 if not repo._activebookmark:
312 312 raise ValueError(
313 313 'headsforactive() only makes sense with an active bookmark')
314 314 name = repo._activebookmark.split('@', 1)[0]
315 315 heads = []
316 316 for mark, n in repo._bookmarks.iteritems():
317 317 if mark.split('@', 1)[0] == name:
318 318 heads.append(n)
319 319 return heads
320 320
321 321 def calculateupdate(ui, repo, checkout):
322 322 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
323 323 check out and where to move the active bookmark from, if needed.'''
324 324 movemarkfrom = None
325 325 if checkout is None:
326 326 activemark = repo._activebookmark
327 327 if isactivewdirparent(repo):
328 328 movemarkfrom = repo['.'].node()
329 329 elif activemark:
330 330 ui.status(_("updating to active bookmark %s\n") % activemark)
331 331 checkout = activemark
332 332 return (checkout, movemarkfrom)
333 333
334 334 def update(repo, parents, node):
335 335 deletefrom = parents
336 336 marks = repo._bookmarks
337 337 update = False
338 338 active = marks.active
339 339 if not active:
340 340 return False
341 341
342 342 if marks[active] in parents:
343 343 new = repo[node]
344 344 divs = [repo[b] for b in marks
345 345 if b.split('@', 1)[0] == active.split('@', 1)[0]]
346 346 anc = repo.changelog.ancestors([new.rev()])
347 347 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
348 348 if validdest(repo, repo[marks[active]], new):
349 349 marks[active] = new.node()
350 350 update = True
351 351
352 352 if deletedivergent(repo, deletefrom, active):
353 353 update = True
354 354
355 355 if update:
356 356 lock = tr = None
357 357 try:
358 358 lock = repo.lock()
359 359 tr = repo.transaction('bookmark')
360 360 marks.recordchange(tr)
361 361 tr.close()
362 362 finally:
363 363 lockmod.release(tr, lock)
364 364 return update
365 365
366 366 def listbinbookmarks(repo):
367 367 # We may try to list bookmarks on a repo type that does not
368 368 # support it (e.g., statichttprepository).
369 369 marks = getattr(repo, '_bookmarks', {})
370 370
371 371 hasnode = repo.changelog.hasnode
372 372 for k, v in marks.iteritems():
373 373 # don't expose local divergent bookmarks
374 374 if hasnode(v) and ('@' not in k or k.endswith('@')):
375 375 yield k, v
376 376
377 377 def listbookmarks(repo):
378 378 d = {}
379 379 for book, node in listbinbookmarks(repo):
380 380 d[book] = hex(node)
381 381 return d
382 382
383 383 def pushbookmark(repo, key, old, new):
384 384 w = l = tr = None
385 385 try:
386 386 w = repo.wlock()
387 387 l = repo.lock()
388 388 tr = repo.transaction('bookmarks')
389 389 marks = repo._bookmarks
390 390 existing = hex(marks.get(key, ''))
391 391 if existing != old and existing != new:
392 392 return False
393 393 if new == '':
394 394 del marks[key]
395 395 else:
396 396 if new not in repo:
397 397 return False
398 398 marks[key] = repo[new].node()
399 399 marks.recordchange(tr)
400 400 tr.close()
401 401 return True
402 402 finally:
403 403 lockmod.release(tr, l, w)
404 404
405 405 def comparebookmarks(repo, srcmarks, dstmarks, targets=None):
406 406 '''Compare bookmarks between srcmarks and dstmarks
407 407
408 408 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
409 409 differ, invalid)", each are list of bookmarks below:
410 410
411 411 :addsrc: added on src side (removed on dst side, perhaps)
412 412 :adddst: added on dst side (removed on src side, perhaps)
413 413 :advsrc: advanced on src side
414 414 :advdst: advanced on dst side
415 415 :diverge: diverge
416 416 :differ: changed, but changeset referred on src is unknown on dst
417 417 :invalid: unknown on both side
418 418 :same: same on both side
419 419
420 420 Each elements of lists in result tuple is tuple "(bookmark name,
421 421 changeset ID on source side, changeset ID on destination
422 422 side)". Each changeset IDs are 40 hexadecimal digit string or
423 423 None.
424 424
425 425 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
426 426 "invalid" list may be unknown for repo.
427 427
428 428 If "targets" is specified, only bookmarks listed in it are
429 429 examined.
430 430 '''
431 431
432 432 if targets:
433 433 bset = set(targets)
434 434 else:
435 435 srcmarkset = set(srcmarks)
436 436 dstmarkset = set(dstmarks)
437 437 bset = srcmarkset | dstmarkset
438 438
439 439 results = ([], [], [], [], [], [], [], [])
440 440 addsrc = results[0].append
441 441 adddst = results[1].append
442 442 advsrc = results[2].append
443 443 advdst = results[3].append
444 444 diverge = results[4].append
445 445 differ = results[5].append
446 446 invalid = results[6].append
447 447 same = results[7].append
448 448
449 449 for b in sorted(bset):
450 450 if b not in srcmarks:
451 451 if b in dstmarks:
452 452 adddst((b, None, dstmarks[b]))
453 453 else:
454 454 invalid((b, None, None))
455 455 elif b not in dstmarks:
456 456 addsrc((b, srcmarks[b], None))
457 457 else:
458 458 scid = srcmarks[b]
459 459 dcid = dstmarks[b]
460 460 if scid == dcid:
461 461 same((b, scid, dcid))
462 462 elif scid in repo and dcid in repo:
463 463 sctx = repo[scid]
464 464 dctx = repo[dcid]
465 465 if sctx.rev() < dctx.rev():
466 466 if validdest(repo, sctx, dctx):
467 467 advdst((b, scid, dcid))
468 468 else:
469 469 diverge((b, scid, dcid))
470 470 else:
471 471 if validdest(repo, dctx, sctx):
472 472 advsrc((b, scid, dcid))
473 473 else:
474 474 diverge((b, scid, dcid))
475 475 else:
476 476 # it is too expensive to examine in detail, in this case
477 477 differ((b, scid, dcid))
478 478
479 479 return results
480 480
481 481 def _diverge(ui, b, path, localmarks, remotenode):
482 482 '''Return appropriate diverged bookmark for specified ``path``
483 483
484 484 This returns None, if it is failed to assign any divergent
485 485 bookmark name.
486 486
487 487 This reuses already existing one with "@number" suffix, if it
488 488 refers ``remotenode``.
489 489 '''
490 490 if b == '@':
491 491 b = ''
492 492 # try to use an @pathalias suffix
493 493 # if an @pathalias already exists, we overwrite (update) it
494 494 if path.startswith("file:"):
495 495 path = util.url(path).path
496 496 for p, u in ui.configitems("paths"):
497 497 if u.startswith("file:"):
498 498 u = util.url(u).path
499 499 if path == u:
500 500 return '%s@%s' % (b, p)
501 501
502 502 # assign a unique "@number" suffix newly
503 503 for x in range(1, 100):
504 504 n = '%s@%d' % (b, x)
505 505 if n not in localmarks or localmarks[n] == remotenode:
506 506 return n
507 507
508 508 return None
509 509
510 510 def unhexlifybookmarks(marks):
511 511 binremotemarks = {}
512 512 for name, node in marks.items():
513 513 binremotemarks[name] = bin(node)
514 514 return binremotemarks
515 515
516 516 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
517 517 ui.debug("checking for updated bookmarks\n")
518 518 localmarks = repo._bookmarks
519 519 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
520 520 ) = comparebookmarks(repo, remotemarks, localmarks)
521 521
522 522 status = ui.status
523 523 warn = ui.warn
524 524 if ui.configbool('ui', 'quietbookmarkmove', False):
525 525 status = warn = ui.debug
526 526
527 527 explicit = set(explicit)
528 528 changed = []
529 529 for b, scid, dcid in addsrc:
530 530 if scid in repo: # add remote bookmarks for changes we already have
531 531 changed.append((b, scid, status,
532 532 _("adding remote bookmark %s\n") % (b)))
533 533 elif b in explicit:
534 534 explicit.remove(b)
535 535 ui.warn(_("remote bookmark %s points to locally missing %s\n")
536 536 % (b, hex(scid)[:12]))
537 537
538 538 for b, scid, dcid in advsrc:
539 539 changed.append((b, scid, status,
540 540 _("updating bookmark %s\n") % (b)))
541 541 # remove normal movement from explicit set
542 542 explicit.difference_update(d[0] for d in changed)
543 543
544 544 for b, scid, dcid in diverge:
545 545 if b in explicit:
546 546 explicit.discard(b)
547 547 changed.append((b, scid, status,
548 548 _("importing bookmark %s\n") % (b)))
549 549 else:
550 550 db = _diverge(ui, b, path, localmarks, scid)
551 551 if db:
552 552 changed.append((db, scid, warn,
553 553 _("divergent bookmark %s stored as %s\n") %
554 554 (b, db)))
555 555 else:
556 556 warn(_("warning: failed to assign numbered name "
557 557 "to divergent bookmark %s\n") % (b))
558 558 for b, scid, dcid in adddst + advdst:
559 559 if b in explicit:
560 560 explicit.discard(b)
561 561 changed.append((b, scid, status,
562 562 _("importing bookmark %s\n") % (b)))
563 563 for b, scid, dcid in differ:
564 564 if b in explicit:
565 565 explicit.remove(b)
566 566 ui.warn(_("remote bookmark %s points to locally missing %s\n")
567 567 % (b, hex(scid)[:12]))
568 568
569 569 if changed:
570 570 tr = trfunc()
571 571 for b, node, writer, msg in sorted(changed):
572 572 localmarks[b] = node
573 573 writer(msg)
574 574 localmarks.recordchange(tr)
575 575
576 576 def incoming(ui, repo, other):
577 577 '''Show bookmarks incoming from other to repo
578 578 '''
579 579 ui.status(_("searching for changed bookmarks\n"))
580 580
581 581 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
582 582 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
583 583 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
584 584
585 585 incomings = []
586 586 if ui.debugflag:
587 587 getid = lambda id: id
588 588 else:
589 589 getid = lambda id: id[:12]
590 590 if ui.verbose:
591 591 def add(b, id, st):
592 592 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
593 593 else:
594 594 def add(b, id, st):
595 595 incomings.append(" %-25s %s\n" % (b, getid(id)))
596 596 for b, scid, dcid in addsrc:
597 597 # i18n: "added" refers to a bookmark
598 598 add(b, hex(scid), _('added'))
599 599 for b, scid, dcid in advsrc:
600 600 # i18n: "advanced" refers to a bookmark
601 601 add(b, hex(scid), _('advanced'))
602 602 for b, scid, dcid in diverge:
603 603 # i18n: "diverged" refers to a bookmark
604 604 add(b, hex(scid), _('diverged'))
605 605 for b, scid, dcid in differ:
606 606 # i18n: "changed" refers to a bookmark
607 607 add(b, hex(scid), _('changed'))
608 608
609 609 if not incomings:
610 610 ui.status(_("no changed bookmarks found\n"))
611 611 return 1
612 612
613 613 for s in sorted(incomings):
614 614 ui.write(s)
615 615
616 616 return 0
617 617
618 618 def outgoing(ui, repo, other):
619 619 '''Show bookmarks outgoing from repo to other
620 620 '''
621 621 ui.status(_("searching for changed bookmarks\n"))
622 622
623 623 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
624 624 r = comparebookmarks(repo, repo._bookmarks, remotemarks)
625 625 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
626 626
627 627 outgoings = []
628 628 if ui.debugflag:
629 629 getid = lambda id: id
630 630 else:
631 631 getid = lambda id: id[:12]
632 632 if ui.verbose:
633 633 def add(b, id, st):
634 634 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
635 635 else:
636 636 def add(b, id, st):
637 637 outgoings.append(" %-25s %s\n" % (b, getid(id)))
638 638 for b, scid, dcid in addsrc:
639 639 # i18n: "added refers to a bookmark
640 640 add(b, hex(scid), _('added'))
641 641 for b, scid, dcid in adddst:
642 642 # i18n: "deleted" refers to a bookmark
643 643 add(b, ' ' * 40, _('deleted'))
644 644 for b, scid, dcid in advsrc:
645 645 # i18n: "advanced" refers to a bookmark
646 646 add(b, hex(scid), _('advanced'))
647 647 for b, scid, dcid in diverge:
648 648 # i18n: "diverged" refers to a bookmark
649 649 add(b, hex(scid), _('diverged'))
650 650 for b, scid, dcid in differ:
651 651 # i18n: "changed" refers to a bookmark
652 652 add(b, hex(scid), _('changed'))
653 653
654 654 if not outgoings:
655 655 ui.status(_("no changed bookmarks found\n"))
656 656 return 1
657 657
658 658 for s in sorted(outgoings):
659 659 ui.write(s)
660 660
661 661 return 0
662 662
663 663 def summary(repo, other):
664 664 '''Compare bookmarks between repo and other for "hg summary" output
665 665
666 666 This returns "(# of incoming, # of outgoing)" tuple.
667 667 '''
668 668 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
669 669 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
670 670 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
671 671 return (len(addsrc), len(adddst))
672 672
673 673 def validdest(repo, old, new):
674 674 """Is the new bookmark destination a valid update from the old one"""
675 675 repo = repo.unfiltered()
676 676 if old == new:
677 677 # Old == new -> nothing to update.
678 678 return False
679 679 elif not old:
680 680 # old is nullrev, anything is valid.
681 681 # (new != nullrev has been excluded by the previous check)
682 682 return True
683 683 elif repo.obsstore:
684 684 return new.node() in obsolete.foreground(repo, [old.node()])
685 685 else:
686 686 # still an independent clause as it is lazier (and therefore faster)
687 687 return old.descendant(new)
688 688
689 689 def checkformat(repo, mark):
690 690 """return a valid version of a potential bookmark name
691 691
692 692 Raises an abort error if the bookmark name is not valid.
693 693 """
694 694 mark = mark.strip()
695 695 if not mark:
696 696 raise error.Abort(_("bookmark names cannot consist entirely of "
697 697 "whitespace"))
698 698 scmutil.checknewlabel(repo, mark, 'bookmark')
699 699 return mark
700 700
701 701 def delete(repo, tr, names):
702 702 """remove a mark from the bookmark store
703 703
704 704 Raises an abort error if mark does not exist.
705 705 """
706 706 marks = repo._bookmarks
707 707 for mark in names:
708 708 if mark not in marks:
709 709 raise error.Abort(_("bookmark '%s' does not exist") % mark)
710 710 if mark == repo._activebookmark:
711 711 deactivate(repo)
712 712 del marks[mark]
713 713 marks.recordchange(tr)
714 714
715 715 def rename(repo, tr, old, new, force=False, inactive=False):
716 716 """rename a bookmark from old to new
717 717
718 718 If force is specified, then the new name can overwrite an existing
719 719 bookmark.
720 720
721 721 If inactive is specified, then do not activate the new bookmark.
722 722
723 723 Raises an abort error if old is not in the bookmark store.
724 724 """
725 725 marks = repo._bookmarks
726 726 mark = checkformat(repo, new)
727 727 if old not in marks:
728 728 raise error.Abort(_("bookmark '%s' does not exist") % old)
729 729 marks.checkconflict(mark, force)
730 730 marks[mark] = marks[old]
731 731 if repo._activebookmark == old and not inactive:
732 732 activate(repo, mark)
733 733 del marks[old]
734 734 marks.recordchange(tr)
735 735
736 736 def addbookmarks(repo, tr, names, rev=None, force=False, inactive=False):
737 737 """add a list of bookmarks
738 738
739 739 If force is specified, then the new name can overwrite an existing
740 740 bookmark.
741 741
742 742 If inactive is specified, then do not activate any bookmark. Otherwise, the
743 743 first bookmark is activated.
744 744
745 745 Raises an abort error if old is not in the bookmark store.
746 746 """
747 747 marks = repo._bookmarks
748 748 cur = repo.changectx('.').node()
749 749 newact = None
750 750 for mark in names:
751 751 mark = checkformat(repo, mark)
752 752 if newact is None:
753 753 newact = mark
754 754 if inactive and mark == repo._activebookmark:
755 755 deactivate(repo)
756 756 return
757 757 tgt = cur
758 758 if rev:
759 759 tgt = scmutil.revsingle(repo, rev).node()
760 760 marks.checkconflict(mark, force, tgt)
761 761 marks[mark] = tgt
762 762 if not inactive and cur == marks[newact] and not rev:
763 763 activate(repo, newact)
764 764 elif cur != tgt and newact == repo._activebookmark:
765 765 deactivate(repo)
766 766 marks.recordchange(tr)
767 767
768 def _printbookmarks(ui, repo, bmarks, **opts):
769 """private method to print bookmarks
770
771 Provides a way for extensions to control how bookmarks are printed (e.g.
772 prepend or postpend names)
773 """
774 fm = ui.formatter('bookmarks', opts)
775 hexfn = fm.hexfunc
776 if len(bmarks) == 0 and fm.isplain():
777 ui.status(_("no bookmarks set\n"))
778 for bmark, (n, prefix, label) in sorted(bmarks.iteritems()):
779 fm.startitem()
780 if not ui.quiet:
781 fm.plain(' %s ' % prefix, label=label)
782 fm.write('bookmark', '%s', bmark, label=label)
783 pad = " " * (25 - encoding.colwidth(bmark))
784 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
785 repo.changelog.rev(n), hexfn(n), label=label)
786 fm.data(active=(activebookmarklabel in label))
787 fm.plain('\n')
788 fm.end()
789
768 790 def printbookmarks(ui, repo, **opts):
769 791 """print bookmarks to a formatter
770 792
771 793 Provides a way for extensions to control how bookmarks are printed.
772 794 """
773 fm = ui.formatter('bookmarks', opts)
774 hexfn = fm.hexfunc
775 795 marks = repo._bookmarks
776 if len(marks) == 0 and fm.isplain():
777 ui.status(_("no bookmarks set\n"))
796 bmarks = {}
778 797 for bmark, n in sorted(marks.iteritems()):
779 798 active = repo._activebookmark
780 799 if bmark == active:
781 800 prefix, label = '*', activebookmarklabel
782 801 else:
783 802 prefix, label = ' ', ''
784 803
785 fm.startitem()
786 if not ui.quiet:
787 fm.plain(' %s ' % prefix, label=label)
788 fm.write('bookmark', '%s', bmark, label=label)
789 pad = " " * (25 - encoding.colwidth(bmark))
790 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
791 repo.changelog.rev(n), hexfn(n), label=label)
792 fm.data(active=(bmark == active))
793 fm.plain('\n')
794 fm.end()
804 bmarks[bmark] = (n, prefix, label)
805 _printbookmarks(ui, repo, bmarks, **opts)
General Comments 0
You need to be logged in to leave comments. Login now