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