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