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