mercurial/bookmarks.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43089 c59eb1560c44
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
    29 
    29 
    30 # label constants
    30 # label constants
    31 # until 3.5, bookmarks.current was the advertised name, not
    31 # until 3.5, bookmarks.current was the advertised name, not
    32 # bookmarks.active, so we must use both to avoid breaking old
    32 # bookmarks.active, so we must use both to avoid breaking old
    33 # custom styles
    33 # custom styles
    34 activebookmarklabel = 'bookmarks.active bookmarks.current'
    34 activebookmarklabel = b'bookmarks.active bookmarks.current'
    35 
    35 
    36 BOOKMARKS_IN_STORE_REQUIREMENT = 'bookmarksinstore'
    36 BOOKMARKS_IN_STORE_REQUIREMENT = b'bookmarksinstore'
    37 
    37 
    38 
    38 
    39 def bookmarksinstore(repo):
    39 def bookmarksinstore(repo):
    40     return BOOKMARKS_IN_STORE_REQUIREMENT in repo.requirements
    40     return BOOKMARKS_IN_STORE_REQUIREMENT in repo.requirements
    41 
    41 
    49 
    49 
    50     For core, this just handles wether we should see pending
    50     For core, this just handles wether we should see pending
    51     bookmarks or the committed ones. Other extensions (like share)
    51     bookmarks or the committed ones. Other extensions (like share)
    52     may need to tweak this behavior further.
    52     may need to tweak this behavior further.
    53     """
    53     """
    54     fp, pending = txnutil.trypending(repo.root, bookmarksvfs(repo), 'bookmarks')
    54     fp, pending = txnutil.trypending(
       
    55         repo.root, bookmarksvfs(repo), b'bookmarks'
       
    56     )
    55     return fp
    57     return fp
    56 
    58 
    57 
    59 
    58 class bmstore(object):
    60 class bmstore(object):
    59     r"""Storage for bookmarks.
    61     r"""Storage for bookmarks.
    82                 for line in bkfile:
    84                 for line in bkfile:
    83                     line = line.strip()
    85                     line = line.strip()
    84                     if not line:
    86                     if not line:
    85                         continue
    87                         continue
    86                     try:
    88                     try:
    87                         sha, refspec = line.split(' ', 1)
    89                         sha, refspec = line.split(b' ', 1)
    88                         node = tonode(sha)
    90                         node = tonode(sha)
    89                         if node in nm:
    91                         if node in nm:
    90                             refspec = encoding.tolocal(refspec)
    92                             refspec = encoding.tolocal(refspec)
    91                             refmap[refspec] = node
    93                             refmap[refspec] = node
    92                             nrefs = nodemap.get(node)
    94                             nrefs = nodemap.get(node)
   101                         # TypeError:
   103                         # TypeError:
   102                         # - bin(...)
   104                         # - bin(...)
   103                         # ValueError:
   105                         # ValueError:
   104                         # - node in nm, for non-20-bytes entry
   106                         # - node in nm, for non-20-bytes entry
   105                         # - split(...), for string without ' '
   107                         # - split(...), for string without ' '
   106                         bookmarkspath = '.hg/bookmarks'
   108                         bookmarkspath = b'.hg/bookmarks'
   107                         if bookmarksinstore(repo):
   109                         if bookmarksinstore(repo):
   108                             bookmarkspath = '.hg/store/bookmarks'
   110                             bookmarkspath = b'.hg/store/bookmarks'
   109                         repo.ui.warn(
   111                         repo.ui.warn(
   110                             _('malformed line in %s: %r\n')
   112                             _(b'malformed line in %s: %r\n')
   111                             % (bookmarkspath, pycompat.bytestr(line))
   113                             % (bookmarkspath, pycompat.bytestr(line))
   112                         )
   114                         )
   113         except IOError as inst:
   115         except IOError as inst:
   114             if inst.errno != errno.ENOENT:
   116             if inst.errno != errno.ENOENT:
   115                 raise
   117                 raise
   120         return self._active
   122         return self._active
   121 
   123 
   122     @active.setter
   124     @active.setter
   123     def active(self, mark):
   125     def active(self, mark):
   124         if mark is not None and mark not in self._refmap:
   126         if mark is not None and mark not in self._refmap:
   125             raise AssertionError('bookmark %s does not exist!' % mark)
   127             raise AssertionError(b'bookmark %s does not exist!' % mark)
   126 
   128 
   127         self._active = mark
   129         self._active = mark
   128         self._aclean = False
   130         self._aclean = False
   129 
   131 
   130     def __len__(self):
   132     def __len__(self):
   184         return self._nodemap.get(node, [])
   186         return self._nodemap.get(node, [])
   185 
   187 
   186     def applychanges(self, repo, tr, changes):
   188     def applychanges(self, repo, tr, changes):
   187         """Apply a list of changes to bookmarks
   189         """Apply a list of changes to bookmarks
   188         """
   190         """
   189         bmchanges = tr.changes.get('bookmarks')
   191         bmchanges = tr.changes.get(b'bookmarks')
   190         for name, node in changes:
   192         for name, node in changes:
   191             old = self._refmap.get(name)
   193             old = self._refmap.get(name)
   192             if node is None:
   194             if node is None:
   193                 self._del(name)
   195                 self._del(name)
   194             else:
   196             else:
   203 
   205 
   204     def _recordchange(self, tr):
   206     def _recordchange(self, tr):
   205         """record that bookmarks have been changed in a transaction
   207         """record that bookmarks have been changed in a transaction
   206 
   208 
   207         The transaction is then responsible for updating the file content."""
   209         The transaction is then responsible for updating the file content."""
   208         location = '' if bookmarksinstore(self._repo) else 'plain'
   210         location = b'' if bookmarksinstore(self._repo) else b'plain'
   209         tr.addfilegenerator(
   211         tr.addfilegenerator(
   210             'bookmarks', ('bookmarks',), self._write, location=location
   212             b'bookmarks', (b'bookmarks',), self._write, location=location
   211         )
   213         )
   212         tr.hookargs['bookmark_moved'] = '1'
   214         tr.hookargs[b'bookmark_moved'] = b'1'
   213 
   215 
   214     def _writerepo(self, repo):
   216     def _writerepo(self, repo):
   215         """Factored out for extensibility"""
   217         """Factored out for extensibility"""
   216         rbm = repo._bookmarks
   218         rbm = repo._bookmarks
   217         if rbm.active not in self._refmap:
   219         if rbm.active not in self._refmap:
   223             lock = repo.lock()
   225             lock = repo.lock()
   224         else:
   226         else:
   225             vfs = repo.vfs
   227             vfs = repo.vfs
   226             lock = repo.wlock()
   228             lock = repo.wlock()
   227         with lock:
   229         with lock:
   228             with vfs('bookmarks', 'w', atomictemp=True, checkambig=True) as f:
   230             with vfs(b'bookmarks', b'w', atomictemp=True, checkambig=True) as f:
   229                 self._write(f)
   231                 self._write(f)
   230 
   232 
   231     def _writeactive(self):
   233     def _writeactive(self):
   232         if self._aclean:
   234         if self._aclean:
   233             return
   235             return
   234         with self._repo.wlock():
   236         with self._repo.wlock():
   235             if self._active is not None:
   237             if self._active is not None:
   236                 with self._repo.vfs(
   238                 with self._repo.vfs(
   237                     'bookmarks.current', 'w', atomictemp=True, checkambig=True
   239                     b'bookmarks.current', b'w', atomictemp=True, checkambig=True
   238                 ) as f:
   240                 ) as f:
   239                     f.write(encoding.fromlocal(self._active))
   241                     f.write(encoding.fromlocal(self._active))
   240             else:
   242             else:
   241                 self._repo.vfs.tryunlink('bookmarks.current')
   243                 self._repo.vfs.tryunlink(b'bookmarks.current')
   242         self._aclean = True
   244         self._aclean = True
   243 
   245 
   244     def _write(self, fp):
   246     def _write(self, fp):
   245         for name, node in sorted(self._refmap.iteritems()):
   247         for name, node in sorted(self._refmap.iteritems()):
   246             fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
   248             fp.write(b"%s %s\n" % (hex(node), encoding.fromlocal(name)))
   247         self._clean = True
   249         self._clean = True
   248         self._repo.invalidatevolatilesets()
   250         self._repo.invalidatevolatilesets()
   249 
   251 
   250     def expandname(self, bname):
   252     def expandname(self, bname):
   251         if bname == '.':
   253         if bname == b'.':
   252             if self.active:
   254             if self.active:
   253                 return self.active
   255                 return self.active
   254             else:
   256             else:
   255                 raise error.RepoLookupError(_("no active bookmark"))
   257                 raise error.RepoLookupError(_(b"no active bookmark"))
   256         return bname
   258         return bname
   257 
   259 
   258     def checkconflict(self, mark, force=False, target=None):
   260     def checkconflict(self, mark, force=False, target=None):
   259         """check repo for a potential clash of mark with an existing bookmark,
   261         """check repo for a potential clash of mark with an existing bookmark,
   260         branch, or hash
   262         branch, or hash
   265         If force is supplied, then forcibly move the bookmark to a new commit
   267         If force is supplied, then forcibly move the bookmark to a new commit
   266         regardless if it is a move forward.
   268         regardless if it is a move forward.
   267 
   269 
   268         If divergent bookmark are to be deleted, they will be returned as list.
   270         If divergent bookmark are to be deleted, they will be returned as list.
   269         """
   271         """
   270         cur = self._repo['.'].node()
   272         cur = self._repo[b'.'].node()
   271         if mark in self._refmap and not force:
   273         if mark in self._refmap and not force:
   272             if target:
   274             if target:
   273                 if self._refmap[mark] == target and target == cur:
   275                 if self._refmap[mark] == target and target == cur:
   274                     # re-activating a bookmark
   276                     # re-activating a bookmark
   275                     return []
   277                     return []
   277                 anc = self._repo.changelog.ancestors([rev])
   279                 anc = self._repo.changelog.ancestors([rev])
   278                 bmctx = self._repo[self[mark]]
   280                 bmctx = self._repo[self[mark]]
   279                 divs = [
   281                 divs = [
   280                     self._refmap[b]
   282                     self._refmap[b]
   281                     for b in self._refmap
   283                     for b in self._refmap
   282                     if b.split('@', 1)[0] == mark.split('@', 1)[0]
   284                     if b.split(b'@', 1)[0] == mark.split(b'@', 1)[0]
   283                 ]
   285                 ]
   284 
   286 
   285                 # allow resolving a single divergent bookmark even if moving
   287                 # allow resolving a single divergent bookmark even if moving
   286                 # the bookmark across branches when a revision is specified
   288                 # the bookmark across branches when a revision is specified
   287                 # that contains a divergent bookmark
   289                 # that contains a divergent bookmark
   292                     b for b in divs if self._repo[b].rev() in anc or b == target
   294                     b for b in divs if self._repo[b].rev() in anc or b == target
   293                 ]
   295                 ]
   294                 delbms = divergent2delete(self._repo, deletefrom, mark)
   296                 delbms = divergent2delete(self._repo, deletefrom, mark)
   295                 if validdest(self._repo, bmctx, self._repo[target]):
   297                 if validdest(self._repo, bmctx, self._repo[target]):
   296                     self._repo.ui.status(
   298                     self._repo.ui.status(
   297                         _("moving bookmark '%s' forward from %s\n")
   299                         _(b"moving bookmark '%s' forward from %s\n")
   298                         % (mark, short(bmctx.node()))
   300                         % (mark, short(bmctx.node()))
   299                     )
   301                     )
   300                     return delbms
   302                     return delbms
   301             raise error.Abort(
   303             raise error.Abort(
   302                 _("bookmark '%s' already exists " "(use -f to force)") % mark
   304                 _(b"bookmark '%s' already exists " b"(use -f to force)") % mark
   303             )
   305             )
   304         if (
   306         if (
   305             mark in self._repo.branchmap()
   307             mark in self._repo.branchmap()
   306             or mark == self._repo.dirstate.branch()
   308             or mark == self._repo.dirstate.branch()
   307         ) and not force:
   309         ) and not force:
   308             raise error.Abort(
   310             raise error.Abort(
   309                 _("a bookmark cannot have the name of an existing branch")
   311                 _(b"a bookmark cannot have the name of an existing branch")
   310             )
   312             )
   311         if len(mark) > 3 and not force:
   313         if len(mark) > 3 and not force:
   312             try:
   314             try:
   313                 shadowhash = scmutil.isrevsymbol(self._repo, mark)
   315                 shadowhash = scmutil.isrevsymbol(self._repo, mark)
   314             except error.LookupError:  # ambiguous identifier
   316             except error.LookupError:  # ambiguous identifier
   315                 shadowhash = False
   317                 shadowhash = False
   316             if shadowhash:
   318             if shadowhash:
   317                 self._repo.ui.warn(
   319                 self._repo.ui.warn(
   318                     _(
   320                     _(
   319                         "bookmark %s matches a changeset hash\n"
   321                         b"bookmark %s matches a changeset hash\n"
   320                         "(did you leave a -r out of an 'hg bookmark' "
   322                         b"(did you leave a -r out of an 'hg bookmark' "
   321                         "command?)\n"
   323                         b"command?)\n"
   322                     )
   324                     )
   323                     % mark
   325                     % mark
   324                 )
   326                 )
   325         return []
   327         return []
   326 
   328 
   331     itself as we commit. This function returns the name of that bookmark.
   333     itself as we commit. This function returns the name of that bookmark.
   332     It is stored in .hg/bookmarks.current
   334     It is stored in .hg/bookmarks.current
   333     """
   335     """
   334     # No readline() in osutil.posixfile, reading everything is
   336     # No readline() in osutil.posixfile, reading everything is
   335     # cheap.
   337     # cheap.
   336     content = repo.vfs.tryread('bookmarks.current')
   338     content = repo.vfs.tryread(b'bookmarks.current')
   337     mark = encoding.tolocal((content.splitlines() or [''])[0])
   339     mark = encoding.tolocal((content.splitlines() or [b''])[0])
   338     if mark == '' or mark not in marks:
   340     if mark == b'' or mark not in marks:
   339         mark = None
   341         mark = None
   340     return mark
   342     return mark
   341 
   343 
   342 
   344 
   343 def activate(repo, mark):
   345 def activate(repo, mark):
   377     """find divergent versions of bm on nodes in deletefrom.
   379     """find divergent versions of bm on nodes in deletefrom.
   378 
   380 
   379     the list of bookmark to delete."""
   381     the list of bookmark to delete."""
   380     todelete = []
   382     todelete = []
   381     marks = repo._bookmarks
   383     marks = repo._bookmarks
   382     divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
   384     divergent = [
       
   385         b for b in marks if b.split(b'@', 1)[0] == bm.split(b'@', 1)[0]
       
   386     ]
   383     for mark in divergent:
   387     for mark in divergent:
   384         if mark == '@' or '@' not in mark:
   388         if mark == b'@' or b'@' not in mark:
   385             # can't be divergent by definition
   389             # can't be divergent by definition
   386             continue
   390             continue
   387         if mark and marks[mark] in deletefrom:
   391         if mark and marks[mark] in deletefrom:
   388             if mark != bm:
   392             if mark != bm:
   389                 todelete.append(mark)
   393                 todelete.append(mark)
   402       there were no divergent bookmarks, then this list will contain
   406       there were no divergent bookmarks, then this list will contain
   403       only one entry.
   407       only one entry.
   404     """
   408     """
   405     if not repo._activebookmark:
   409     if not repo._activebookmark:
   406         raise ValueError(
   410         raise ValueError(
   407             'headsforactive() only makes sense with an active bookmark'
   411             b'headsforactive() only makes sense with an active bookmark'
   408         )
   412         )
   409     name = repo._activebookmark.split('@', 1)[0]
   413     name = repo._activebookmark.split(b'@', 1)[0]
   410     heads = []
   414     heads = []
   411     for mark, n in repo._bookmarks.iteritems():
   415     for mark, n in repo._bookmarks.iteritems():
   412         if mark.split('@', 1)[0] == name:
   416         if mark.split(b'@', 1)[0] == name:
   413             heads.append(n)
   417             heads.append(n)
   414     return heads
   418     return heads
   415 
   419 
   416 
   420 
   417 def calculateupdate(ui, repo):
   421 def calculateupdate(ui, repo):
   418     '''Return a tuple (activemark, movemarkfrom) indicating the active bookmark
   422     '''Return a tuple (activemark, movemarkfrom) indicating the active bookmark
   419     and where to move the active bookmark from, if needed.'''
   423     and where to move the active bookmark from, if needed.'''
   420     checkout, movemarkfrom = None, None
   424     checkout, movemarkfrom = None, None
   421     activemark = repo._activebookmark
   425     activemark = repo._activebookmark
   422     if isactivewdirparent(repo):
   426     if isactivewdirparent(repo):
   423         movemarkfrom = repo['.'].node()
   427         movemarkfrom = repo[b'.'].node()
   424     elif activemark:
   428     elif activemark:
   425         ui.status(_("updating to active bookmark %s\n") % activemark)
   429         ui.status(_(b"updating to active bookmark %s\n") % activemark)
   426         checkout = activemark
   430         checkout = activemark
   427     return (checkout, movemarkfrom)
   431     return (checkout, movemarkfrom)
   428 
   432 
   429 
   433 
   430 def update(repo, parents, node):
   434 def update(repo, parents, node):
   438     if marks[active] in parents:
   442     if marks[active] in parents:
   439         new = repo[node]
   443         new = repo[node]
   440         divs = [
   444         divs = [
   441             repo[marks[b]]
   445             repo[marks[b]]
   442             for b in marks
   446             for b in marks
   443             if b.split('@', 1)[0] == active.split('@', 1)[0]
   447             if b.split(b'@', 1)[0] == active.split(b'@', 1)[0]
   444         ]
   448         ]
   445         anc = repo.changelog.ancestors([new.rev()])
   449         anc = repo.changelog.ancestors([new.rev()])
   446         deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
   450         deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
   447         if validdest(repo, repo[marks[active]], new):
   451         if validdest(repo, repo[marks[active]], new):
   448             bmchanges.append((active, new.node()))
   452             bmchanges.append((active, new.node()))
   449 
   453 
   450     for bm in divergent2delete(repo, deletefrom, active):
   454     for bm in divergent2delete(repo, deletefrom, active):
   451         bmchanges.append((bm, None))
   455         bmchanges.append((bm, None))
   452 
   456 
   453     if bmchanges:
   457     if bmchanges:
   454         with repo.lock(), repo.transaction('bookmark') as tr:
   458         with repo.lock(), repo.transaction(b'bookmark') as tr:
   455             marks.applychanges(repo, tr, bmchanges)
   459             marks.applychanges(repo, tr, bmchanges)
   456     return bool(bmchanges)
   460     return bool(bmchanges)
   457 
   461 
   458 
   462 
   459 def listbinbookmarks(repo):
   463 def listbinbookmarks(repo):
   462     marks = getattr(repo, '_bookmarks', {})
   466     marks = getattr(repo, '_bookmarks', {})
   463 
   467 
   464     hasnode = repo.changelog.hasnode
   468     hasnode = repo.changelog.hasnode
   465     for k, v in marks.iteritems():
   469     for k, v in marks.iteritems():
   466         # don't expose local divergent bookmarks
   470         # don't expose local divergent bookmarks
   467         if hasnode(v) and ('@' not in k or k.endswith('@')):
   471         if hasnode(v) and (b'@' not in k or k.endswith(b'@')):
   468             yield k, v
   472             yield k, v
   469 
   473 
   470 
   474 
   471 def listbookmarks(repo):
   475 def listbookmarks(repo):
   472     d = {}
   476     d = {}
   478 def pushbookmark(repo, key, old, new):
   482 def pushbookmark(repo, key, old, new):
   479     if bookmarksinstore(repo):
   483     if bookmarksinstore(repo):
   480         wlock = util.nullcontextmanager()
   484         wlock = util.nullcontextmanager()
   481     else:
   485     else:
   482         wlock = repo.wlock()
   486         wlock = repo.wlock()
   483     with wlock, repo.lock(), repo.transaction('bookmarks') as tr:
   487     with wlock, repo.lock(), repo.transaction(b'bookmarks') as tr:
   484         marks = repo._bookmarks
   488         marks = repo._bookmarks
   485         existing = hex(marks.get(key, ''))
   489         existing = hex(marks.get(key, b''))
   486         if existing != old and existing != new:
   490         if existing != old and existing != new:
   487             return False
   491             return False
   488         if new == '':
   492         if new == b'':
   489             changes = [(key, None)]
   493             changes = [(key, None)]
   490         else:
   494         else:
   491             if new not in repo:
   495             if new not in repo:
   492                 return False
   496                 return False
   493             changes = [(key, repo[new].node())]
   497             changes = [(key, repo[new].node())]
   578     bookmark name.
   582     bookmark name.
   579 
   583 
   580     This reuses already existing one with "@number" suffix, if it
   584     This reuses already existing one with "@number" suffix, if it
   581     refers ``remotenode``.
   585     refers ``remotenode``.
   582     '''
   586     '''
   583     if b == '@':
   587     if b == b'@':
   584         b = ''
   588         b = b''
   585     # try to use an @pathalias suffix
   589     # try to use an @pathalias suffix
   586     # if an @pathalias already exists, we overwrite (update) it
   590     # if an @pathalias already exists, we overwrite (update) it
   587     if path.startswith("file:"):
   591     if path.startswith(b"file:"):
   588         path = util.url(path).path
   592         path = util.url(path).path
   589     for p, u in ui.configitems("paths"):
   593     for p, u in ui.configitems(b"paths"):
   590         if u.startswith("file:"):
   594         if u.startswith(b"file:"):
   591             u = util.url(u).path
   595             u = util.url(u).path
   592         if path == u:
   596         if path == u:
   593             return '%s@%s' % (b, p)
   597             return b'%s@%s' % (b, p)
   594 
   598 
   595     # assign a unique "@number" suffix newly
   599     # assign a unique "@number" suffix newly
   596     for x in range(1, 100):
   600     for x in range(1, 100):
   597         n = '%s@%d' % (b, x)
   601         n = b'%s@%d' % (b, x)
   598         if n not in localmarks or localmarks[n] == remotenode:
   602         if n not in localmarks or localmarks[n] == remotenode:
   599             return n
   603             return n
   600 
   604 
   601     return None
   605     return None
   602 
   606 
   606     for name, node in marks.items():
   610     for name, node in marks.items():
   607         binremotemarks[name] = bin(node)
   611         binremotemarks[name] = bin(node)
   608     return binremotemarks
   612     return binremotemarks
   609 
   613 
   610 
   614 
   611 _binaryentry = struct.Struct('>20sH')
   615 _binaryentry = struct.Struct(b'>20sH')
   612 
   616 
   613 
   617 
   614 def binaryencode(bookmarks):
   618 def binaryencode(bookmarks):
   615     """encode a '(bookmark, node)' iterable into a binary stream
   619     """encode a '(bookmark, node)' iterable into a binary stream
   616 
   620 
   628     for book, node in bookmarks:
   632     for book, node in bookmarks:
   629         if not node:  # None or ''
   633         if not node:  # None or ''
   630             node = wdirid
   634             node = wdirid
   631         binarydata.append(_binaryentry.pack(node, len(book)))
   635         binarydata.append(_binaryentry.pack(node, len(book)))
   632         binarydata.append(book)
   636         binarydata.append(book)
   633     return ''.join(binarydata)
   637     return b''.join(binarydata)
   634 
   638 
   635 
   639 
   636 def binarydecode(stream):
   640 def binarydecode(stream):
   637     """decode a binary stream into an '(bookmark, node)' iterable
   641     """decode a binary stream into an '(bookmark, node)' iterable
   638 
   642 
   650     books = []
   654     books = []
   651     while True:
   655     while True:
   652         entry = stream.read(entrysize)
   656         entry = stream.read(entrysize)
   653         if len(entry) < entrysize:
   657         if len(entry) < entrysize:
   654             if entry:
   658             if entry:
   655                 raise error.Abort(_('bad bookmark stream'))
   659                 raise error.Abort(_(b'bad bookmark stream'))
   656             break
   660             break
   657         node, length = _binaryentry.unpack(entry)
   661         node, length = _binaryentry.unpack(entry)
   658         bookmark = stream.read(length)
   662         bookmark = stream.read(length)
   659         if len(bookmark) < length:
   663         if len(bookmark) < length:
   660             if entry:
   664             if entry:
   661                 raise error.Abort(_('bad bookmark stream'))
   665                 raise error.Abort(_(b'bad bookmark stream'))
   662         if node == wdirid:
   666         if node == wdirid:
   663             node = None
   667             node = None
   664         books.append((bookmark, node))
   668         books.append((bookmark, node))
   665     return books
   669     return books
   666 
   670 
   667 
   671 
   668 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
   672 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
   669     ui.debug("checking for updated bookmarks\n")
   673     ui.debug(b"checking for updated bookmarks\n")
   670     localmarks = repo._bookmarks
   674     localmarks = repo._bookmarks
   671     (
   675     (
   672         addsrc,
   676         addsrc,
   673         adddst,
   677         adddst,
   674         advsrc,
   678         advsrc,
   679         same,
   683         same,
   680     ) = comparebookmarks(repo, remotemarks, localmarks)
   684     ) = comparebookmarks(repo, remotemarks, localmarks)
   681 
   685 
   682     status = ui.status
   686     status = ui.status
   683     warn = ui.warn
   687     warn = ui.warn
   684     if ui.configbool('ui', 'quietbookmarkmove'):
   688     if ui.configbool(b'ui', b'quietbookmarkmove'):
   685         status = warn = ui.debug
   689         status = warn = ui.debug
   686 
   690 
   687     explicit = set(explicit)
   691     explicit = set(explicit)
   688     changed = []
   692     changed = []
   689     for b, scid, dcid in addsrc:
   693     for b, scid, dcid in addsrc:
   690         if scid in repo:  # add remote bookmarks for changes we already have
   694         if scid in repo:  # add remote bookmarks for changes we already have
   691             changed.append(
   695             changed.append(
   692                 (b, scid, status, _("adding remote bookmark %s\n") % b)
   696                 (b, scid, status, _(b"adding remote bookmark %s\n") % b)
   693             )
   697             )
   694         elif b in explicit:
   698         elif b in explicit:
   695             explicit.remove(b)
   699             explicit.remove(b)
   696             ui.warn(
   700             ui.warn(
   697                 _("remote bookmark %s points to locally missing %s\n")
   701                 _(b"remote bookmark %s points to locally missing %s\n")
   698                 % (b, hex(scid)[:12])
   702                 % (b, hex(scid)[:12])
   699             )
   703             )
   700 
   704 
   701     for b, scid, dcid in advsrc:
   705     for b, scid, dcid in advsrc:
   702         changed.append((b, scid, status, _("updating bookmark %s\n") % b))
   706         changed.append((b, scid, status, _(b"updating bookmark %s\n") % b))
   703     # remove normal movement from explicit set
   707     # remove normal movement from explicit set
   704     explicit.difference_update(d[0] for d in changed)
   708     explicit.difference_update(d[0] for d in changed)
   705 
   709 
   706     for b, scid, dcid in diverge:
   710     for b, scid, dcid in diverge:
   707         if b in explicit:
   711         if b in explicit:
   708             explicit.discard(b)
   712             explicit.discard(b)
   709             changed.append((b, scid, status, _("importing bookmark %s\n") % b))
   713             changed.append((b, scid, status, _(b"importing bookmark %s\n") % b))
   710         else:
   714         else:
   711             db = _diverge(ui, b, path, localmarks, scid)
   715             db = _diverge(ui, b, path, localmarks, scid)
   712             if db:
   716             if db:
   713                 changed.append(
   717                 changed.append(
   714                     (
   718                     (
   715                         db,
   719                         db,
   716                         scid,
   720                         scid,
   717                         warn,
   721                         warn,
   718                         _("divergent bookmark %s stored as %s\n") % (b, db),
   722                         _(b"divergent bookmark %s stored as %s\n") % (b, db),
   719                     )
   723                     )
   720                 )
   724                 )
   721             else:
   725             else:
   722                 warn(
   726                 warn(
   723                     _(
   727                     _(
   724                         "warning: failed to assign numbered name "
   728                         b"warning: failed to assign numbered name "
   725                         "to divergent bookmark %s\n"
   729                         b"to divergent bookmark %s\n"
   726                     )
   730                     )
   727                     % b
   731                     % b
   728                 )
   732                 )
   729     for b, scid, dcid in adddst + advdst:
   733     for b, scid, dcid in adddst + advdst:
   730         if b in explicit:
   734         if b in explicit:
   731             explicit.discard(b)
   735             explicit.discard(b)
   732             changed.append((b, scid, status, _("importing bookmark %s\n") % b))
   736             changed.append((b, scid, status, _(b"importing bookmark %s\n") % b))
   733     for b, scid, dcid in differ:
   737     for b, scid, dcid in differ:
   734         if b in explicit:
   738         if b in explicit:
   735             explicit.remove(b)
   739             explicit.remove(b)
   736             ui.warn(
   740             ui.warn(
   737                 _("remote bookmark %s points to locally missing %s\n")
   741                 _(b"remote bookmark %s points to locally missing %s\n")
   738                 % (b, hex(scid)[:12])
   742                 % (b, hex(scid)[:12])
   739             )
   743             )
   740 
   744 
   741     if changed:
   745     if changed:
   742         tr = trfunc()
   746         tr = trfunc()
   748 
   752 
   749 
   753 
   750 def incoming(ui, repo, peer):
   754 def incoming(ui, repo, peer):
   751     '''Show bookmarks incoming from other to repo
   755     '''Show bookmarks incoming from other to repo
   752     '''
   756     '''
   753     ui.status(_("searching for changed bookmarks\n"))
   757     ui.status(_(b"searching for changed bookmarks\n"))
   754 
   758 
   755     with peer.commandexecutor() as e:
   759     with peer.commandexecutor() as e:
   756         remotemarks = unhexlifybookmarks(
   760         remotemarks = unhexlifybookmarks(
   757             e.callcommand('listkeys', {'namespace': 'bookmarks',}).result()
   761             e.callcommand(b'listkeys', {b'namespace': b'bookmarks',}).result()
   758         )
   762         )
   759 
   763 
   760     r = comparebookmarks(repo, remotemarks, repo._bookmarks)
   764     r = comparebookmarks(repo, remotemarks, repo._bookmarks)
   761     addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
   765     addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
   762 
   766 
   766     else:
   770     else:
   767         getid = lambda id: id[:12]
   771         getid = lambda id: id[:12]
   768     if ui.verbose:
   772     if ui.verbose:
   769 
   773 
   770         def add(b, id, st):
   774         def add(b, id, st):
   771             incomings.append("   %-25s %s %s\n" % (b, getid(id), st))
   775             incomings.append(b"   %-25s %s %s\n" % (b, getid(id), st))
   772 
   776 
   773     else:
   777     else:
   774 
   778 
   775         def add(b, id, st):
   779         def add(b, id, st):
   776             incomings.append("   %-25s %s\n" % (b, getid(id)))
   780             incomings.append(b"   %-25s %s\n" % (b, getid(id)))
   777 
   781 
   778     for b, scid, dcid in addsrc:
   782     for b, scid, dcid in addsrc:
   779         # i18n: "added" refers to a bookmark
   783         # i18n: "added" refers to a bookmark
   780         add(b, hex(scid), _('added'))
   784         add(b, hex(scid), _(b'added'))
   781     for b, scid, dcid in advsrc:
   785     for b, scid, dcid in advsrc:
   782         # i18n: "advanced" refers to a bookmark
   786         # i18n: "advanced" refers to a bookmark
   783         add(b, hex(scid), _('advanced'))
   787         add(b, hex(scid), _(b'advanced'))
   784     for b, scid, dcid in diverge:
   788     for b, scid, dcid in diverge:
   785         # i18n: "diverged" refers to a bookmark
   789         # i18n: "diverged" refers to a bookmark
   786         add(b, hex(scid), _('diverged'))
   790         add(b, hex(scid), _(b'diverged'))
   787     for b, scid, dcid in differ:
   791     for b, scid, dcid in differ:
   788         # i18n: "changed" refers to a bookmark
   792         # i18n: "changed" refers to a bookmark
   789         add(b, hex(scid), _('changed'))
   793         add(b, hex(scid), _(b'changed'))
   790 
   794 
   791     if not incomings:
   795     if not incomings:
   792         ui.status(_("no changed bookmarks found\n"))
   796         ui.status(_(b"no changed bookmarks found\n"))
   793         return 1
   797         return 1
   794 
   798 
   795     for s in sorted(incomings):
   799     for s in sorted(incomings):
   796         ui.write(s)
   800         ui.write(s)
   797 
   801 
   799 
   803 
   800 
   804 
   801 def outgoing(ui, repo, other):
   805 def outgoing(ui, repo, other):
   802     '''Show bookmarks outgoing from repo to other
   806     '''Show bookmarks outgoing from repo to other
   803     '''
   807     '''
   804     ui.status(_("searching for changed bookmarks\n"))
   808     ui.status(_(b"searching for changed bookmarks\n"))
   805 
   809 
   806     remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
   810     remotemarks = unhexlifybookmarks(other.listkeys(b'bookmarks'))
   807     r = comparebookmarks(repo, repo._bookmarks, remotemarks)
   811     r = comparebookmarks(repo, repo._bookmarks, remotemarks)
   808     addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
   812     addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
   809 
   813 
   810     outgoings = []
   814     outgoings = []
   811     if ui.debugflag:
   815     if ui.debugflag:
   813     else:
   817     else:
   814         getid = lambda id: id[:12]
   818         getid = lambda id: id[:12]
   815     if ui.verbose:
   819     if ui.verbose:
   816 
   820 
   817         def add(b, id, st):
   821         def add(b, id, st):
   818             outgoings.append("   %-25s %s %s\n" % (b, getid(id), st))
   822             outgoings.append(b"   %-25s %s %s\n" % (b, getid(id), st))
   819 
   823 
   820     else:
   824     else:
   821 
   825 
   822         def add(b, id, st):
   826         def add(b, id, st):
   823             outgoings.append("   %-25s %s\n" % (b, getid(id)))
   827             outgoings.append(b"   %-25s %s\n" % (b, getid(id)))
   824 
   828 
   825     for b, scid, dcid in addsrc:
   829     for b, scid, dcid in addsrc:
   826         # i18n: "added refers to a bookmark
   830         # i18n: "added refers to a bookmark
   827         add(b, hex(scid), _('added'))
   831         add(b, hex(scid), _(b'added'))
   828     for b, scid, dcid in adddst:
   832     for b, scid, dcid in adddst:
   829         # i18n: "deleted" refers to a bookmark
   833         # i18n: "deleted" refers to a bookmark
   830         add(b, ' ' * 40, _('deleted'))
   834         add(b, b' ' * 40, _(b'deleted'))
   831     for b, scid, dcid in advsrc:
   835     for b, scid, dcid in advsrc:
   832         # i18n: "advanced" refers to a bookmark
   836         # i18n: "advanced" refers to a bookmark
   833         add(b, hex(scid), _('advanced'))
   837         add(b, hex(scid), _(b'advanced'))
   834     for b, scid, dcid in diverge:
   838     for b, scid, dcid in diverge:
   835         # i18n: "diverged" refers to a bookmark
   839         # i18n: "diverged" refers to a bookmark
   836         add(b, hex(scid), _('diverged'))
   840         add(b, hex(scid), _(b'diverged'))
   837     for b, scid, dcid in differ:
   841     for b, scid, dcid in differ:
   838         # i18n: "changed" refers to a bookmark
   842         # i18n: "changed" refers to a bookmark
   839         add(b, hex(scid), _('changed'))
   843         add(b, hex(scid), _(b'changed'))
   840 
   844 
   841     if not outgoings:
   845     if not outgoings:
   842         ui.status(_("no changed bookmarks found\n"))
   846         ui.status(_(b"no changed bookmarks found\n"))
   843         return 1
   847         return 1
   844 
   848 
   845     for s in sorted(outgoings):
   849     for s in sorted(outgoings):
   846         ui.write(s)
   850         ui.write(s)
   847 
   851 
   853 
   857 
   854     This returns "(# of incoming, # of outgoing)" tuple.
   858     This returns "(# of incoming, # of outgoing)" tuple.
   855     '''
   859     '''
   856     with peer.commandexecutor() as e:
   860     with peer.commandexecutor() as e:
   857         remotemarks = unhexlifybookmarks(
   861         remotemarks = unhexlifybookmarks(
   858             e.callcommand('listkeys', {'namespace': 'bookmarks',}).result()
   862             e.callcommand(b'listkeys', {b'namespace': b'bookmarks',}).result()
   859         )
   863         )
   860 
   864 
   861     r = comparebookmarks(repo, remotemarks, repo._bookmarks)
   865     r = comparebookmarks(repo, remotemarks, repo._bookmarks)
   862     addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
   866     addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
   863     return (len(addsrc), len(adddst))
   867     return (len(addsrc), len(adddst))
   886     Raises an abort error if the bookmark name is not valid.
   890     Raises an abort error if the bookmark name is not valid.
   887     """
   891     """
   888     mark = mark.strip()
   892     mark = mark.strip()
   889     if not mark:
   893     if not mark:
   890         raise error.Abort(
   894         raise error.Abort(
   891             _("bookmark names cannot consist entirely of " "whitespace")
   895             _(b"bookmark names cannot consist entirely of " b"whitespace")
   892         )
   896         )
   893     scmutil.checknewlabel(repo, mark, 'bookmark')
   897     scmutil.checknewlabel(repo, mark, b'bookmark')
   894     return mark
   898     return mark
   895 
   899 
   896 
   900 
   897 def delete(repo, tr, names):
   901 def delete(repo, tr, names):
   898     """remove a mark from the bookmark store
   902     """remove a mark from the bookmark store
   901     """
   905     """
   902     marks = repo._bookmarks
   906     marks = repo._bookmarks
   903     changes = []
   907     changes = []
   904     for mark in names:
   908     for mark in names:
   905         if mark not in marks:
   909         if mark not in marks:
   906             raise error.Abort(_("bookmark '%s' does not exist") % mark)
   910             raise error.Abort(_(b"bookmark '%s' does not exist") % mark)
   907         if mark == repo._activebookmark:
   911         if mark == repo._activebookmark:
   908             deactivate(repo)
   912             deactivate(repo)
   909         changes.append((mark, None))
   913         changes.append((mark, None))
   910     marks.applychanges(repo, tr, changes)
   914     marks.applychanges(repo, tr, changes)
   911 
   915 
   921     Raises an abort error if old is not in the bookmark store.
   925     Raises an abort error if old is not in the bookmark store.
   922     """
   926     """
   923     marks = repo._bookmarks
   927     marks = repo._bookmarks
   924     mark = checkformat(repo, new)
   928     mark = checkformat(repo, new)
   925     if old not in marks:
   929     if old not in marks:
   926         raise error.Abort(_("bookmark '%s' does not exist") % old)
   930         raise error.Abort(_(b"bookmark '%s' does not exist") % old)
   927     changes = []
   931     changes = []
   928     for bm in marks.checkconflict(mark, force):
   932     for bm in marks.checkconflict(mark, force):
   929         changes.append((bm, None))
   933         changes.append((bm, None))
   930     changes.extend([(mark, marks[old]), (old, None)])
   934     changes.extend([(mark, marks[old]), (old, None)])
   931     marks.applychanges(repo, tr, changes)
   935     marks.applychanges(repo, tr, changes)
   943     first bookmark is activated.
   947     first bookmark is activated.
   944 
   948 
   945     Raises an abort error if old is not in the bookmark store.
   949     Raises an abort error if old is not in the bookmark store.
   946     """
   950     """
   947     marks = repo._bookmarks
   951     marks = repo._bookmarks
   948     cur = repo['.'].node()
   952     cur = repo[b'.'].node()
   949     newact = None
   953     newact = None
   950     changes = []
   954     changes = []
   951     hiddenrev = None
   955     hiddenrev = None
   952 
   956 
   953     # unhide revs if any
   957     # unhide revs if any
   954     if rev:
   958     if rev:
   955         repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
   959         repo = scmutil.unhidehashlikerevs(repo, [rev], b'nowarn')
   956 
   960 
   957     for mark in names:
   961     for mark in names:
   958         mark = checkformat(repo, mark)
   962         mark = checkformat(repo, mark)
   959         if newact is None:
   963         if newact is None:
   960             newact = mark
   964             newact = mark
   970         for bm in marks.checkconflict(mark, force, tgt):
   974         for bm in marks.checkconflict(mark, force, tgt):
   971             changes.append((bm, None))
   975             changes.append((bm, None))
   972         changes.append((mark, tgt))
   976         changes.append((mark, tgt))
   973 
   977 
   974     if hiddenrev:
   978     if hiddenrev:
   975         repo.ui.warn(_("bookmarking hidden changeset %s\n") % hiddenrev)
   979         repo.ui.warn(_(b"bookmarking hidden changeset %s\n") % hiddenrev)
   976 
   980 
   977         if ctx.obsolete():
   981         if ctx.obsolete():
   978             msg = obsutil._getfilteredreason(repo, "%s" % hiddenrev, ctx)
   982             msg = obsutil._getfilteredreason(repo, b"%s" % hiddenrev, ctx)
   979             repo.ui.warn("(%s)\n" % msg)
   983             repo.ui.warn(b"(%s)\n" % msg)
   980 
   984 
   981     marks.applychanges(repo, tr, changes)
   985     marks.applychanges(repo, tr, changes)
   982     if not inactive and cur == marks[newact] and not rev:
   986     if not inactive and cur == marks[newact] and not rev:
   983         activate(repo, newact)
   987         activate(repo, newact)
   984     elif cur != tgt and newact == repo._activebookmark:
   988     elif cur != tgt and newact == repo._activebookmark:
   991     Provides a way for extensions to control how bookmarks are printed (e.g.
   995     Provides a way for extensions to control how bookmarks are printed (e.g.
   992     prepend or postpend names)
   996     prepend or postpend names)
   993     """
   997     """
   994     hexfn = fm.hexfunc
   998     hexfn = fm.hexfunc
   995     if len(bmarks) == 0 and fm.isplain():
   999     if len(bmarks) == 0 and fm.isplain():
   996         ui.status(_("no bookmarks set\n"))
  1000         ui.status(_(b"no bookmarks set\n"))
   997     for bmark, (n, prefix, label) in sorted(bmarks.iteritems()):
  1001     for bmark, (n, prefix, label) in sorted(bmarks.iteritems()):
   998         fm.startitem()
  1002         fm.startitem()
   999         fm.context(repo=repo)
  1003         fm.context(repo=repo)
  1000         if not ui.quiet:
  1004         if not ui.quiet:
  1001             fm.plain(' %s ' % prefix, label=label)
  1005             fm.plain(b' %s ' % prefix, label=label)
  1002         fm.write('bookmark', '%s', bmark, label=label)
  1006         fm.write(b'bookmark', b'%s', bmark, label=label)
  1003         pad = " " * (25 - encoding.colwidth(bmark))
  1007         pad = b" " * (25 - encoding.colwidth(bmark))
  1004         fm.condwrite(
  1008         fm.condwrite(
  1005             not ui.quiet,
  1009             not ui.quiet,
  1006             'rev node',
  1010             b'rev node',
  1007             pad + ' %d:%s',
  1011             pad + b' %d:%s',
  1008             repo.changelog.rev(n),
  1012             repo.changelog.rev(n),
  1009             hexfn(n),
  1013             hexfn(n),
  1010             label=label,
  1014             label=label,
  1011         )
  1015         )
  1012         fm.data(active=(activebookmarklabel in label))
  1016         fm.data(active=(activebookmarklabel in label))
  1013         fm.plain('\n')
  1017         fm.plain(b'\n')
  1014 
  1018 
  1015 
  1019 
  1016 def printbookmarks(ui, repo, fm, names=None):
  1020 def printbookmarks(ui, repo, fm, names=None):
  1017     """print bookmarks by the given formatter
  1021     """print bookmarks by the given formatter
  1018 
  1022 
  1020     """
  1024     """
  1021     marks = repo._bookmarks
  1025     marks = repo._bookmarks
  1022     bmarks = {}
  1026     bmarks = {}
  1023     for bmark in names or marks:
  1027     for bmark in names or marks:
  1024         if bmark not in marks:
  1028         if bmark not in marks:
  1025             raise error.Abort(_("bookmark '%s' does not exist") % bmark)
  1029             raise error.Abort(_(b"bookmark '%s' does not exist") % bmark)
  1026         active = repo._activebookmark
  1030         active = repo._activebookmark
  1027         if bmark == active:
  1031         if bmark == active:
  1028             prefix, label = '*', activebookmarklabel
  1032             prefix, label = b'*', activebookmarklabel
  1029         else:
  1033         else:
  1030             prefix, label = ' ', ''
  1034             prefix, label = b' ', b''
  1031 
  1035 
  1032         bmarks[bmark] = (marks[bmark], prefix, label)
  1036         bmarks[bmark] = (marks[bmark], prefix, label)
  1033     _printbookmarks(ui, repo, fm, bmarks)
  1037     _printbookmarks(ui, repo, fm, bmarks)
  1034 
  1038 
  1035 
  1039 
  1036 def preparehookargs(name, old, new):
  1040 def preparehookargs(name, old, new):
  1037     if new is None:
  1041     if new is None:
  1038         new = ''
  1042         new = b''
  1039     if old is None:
  1043     if old is None:
  1040         old = ''
  1044         old = b''
  1041     return {'bookmark': name, 'node': hex(new), 'oldnode': hex(old)}
  1045     return {b'bookmark': name, b'node': hex(new), b'oldnode': hex(old)}