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