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