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