hgext/bookmarks.py
branchstable
changeset 13427 2432b3227303
parent 13369 69238d0ca60f
parent 13426 643b8212813e
child 13428 5ef29e0dd418
equal deleted inserted replaced
13369:69238d0ca60f 13427:2432b3227303
     1 # Mercurial extension to provide the 'hg bookmark' command
       
     2 #
       
     3 # Copyright 2008 David Soria Parra <dsp@php.net>
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 '''track a line of development with movable markers
       
     9 
       
    10 Bookmarks are local movable markers to changesets. Every bookmark
       
    11 points to a changeset identified by its hash. If you commit a
       
    12 changeset that is based on a changeset that has a bookmark on it, the
       
    13 bookmark shifts to the new changeset.
       
    14 
       
    15 It is possible to use bookmark names in every revision lookup (e.g.
       
    16 :hg:`merge`, :hg:`update`).
       
    17 
       
    18 By default, when several bookmarks point to the same changeset, they
       
    19 will all move forward together. It is possible to obtain a more
       
    20 git-like experience by adding the following configuration option to
       
    21 your configuration file::
       
    22 
       
    23   [bookmarks]
       
    24   track.current = True
       
    25 
       
    26 This will cause Mercurial to track the bookmark that you are currently
       
    27 using, and only update it. This is similar to git's approach to
       
    28 branching.
       
    29 '''
       
    30 
       
    31 from mercurial.i18n import _
       
    32 from mercurial.node import nullid, nullrev, bin, hex, short
       
    33 from mercurial import util, commands, repair, extensions, pushkey, hg, url
       
    34 from mercurial import revset
       
    35 import os
       
    36 
       
    37 def write(repo):
       
    38     '''Write bookmarks
       
    39 
       
    40     Write the given bookmark => hash dictionary to the .hg/bookmarks file
       
    41     in a format equal to those of localtags.
       
    42 
       
    43     We also store a backup of the previous state in undo.bookmarks that
       
    44     can be copied back on rollback.
       
    45     '''
       
    46     refs = repo._bookmarks
       
    47 
       
    48     try:
       
    49         bms = repo.opener('bookmarks').read()
       
    50     except IOError:
       
    51         bms = ''
       
    52     repo.opener('undo.bookmarks', 'w').write(bms)
       
    53 
       
    54     if repo._bookmarkcurrent not in refs:
       
    55         setcurrent(repo, None)
       
    56     wlock = repo.wlock()
       
    57     try:
       
    58         file = repo.opener('bookmarks', 'w', atomictemp=True)
       
    59         for refspec, node in refs.iteritems():
       
    60             file.write("%s %s\n" % (hex(node), refspec))
       
    61         file.rename()
       
    62 
       
    63         # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
       
    64         try:
       
    65             os.utime(repo.sjoin('00changelog.i'), None)
       
    66         except OSError:
       
    67             pass
       
    68 
       
    69     finally:
       
    70         wlock.release()
       
    71 
       
    72 def setcurrent(repo, mark):
       
    73     '''Set the name of the bookmark that we are currently on
       
    74 
       
    75     Set the name of the bookmark that we are on (hg update <bookmark>).
       
    76     The name is recorded in .hg/bookmarks.current
       
    77     '''
       
    78     current = repo._bookmarkcurrent
       
    79     if current == mark:
       
    80         return
       
    81 
       
    82     refs = repo._bookmarks
       
    83 
       
    84     # do not update if we do update to a rev equal to the current bookmark
       
    85     if (mark and mark not in refs and
       
    86         current and refs[current] == repo.changectx('.').node()):
       
    87         return
       
    88     if mark not in refs:
       
    89         mark = ''
       
    90     wlock = repo.wlock()
       
    91     try:
       
    92         file = repo.opener('bookmarks.current', 'w', atomictemp=True)
       
    93         file.write(mark)
       
    94         file.rename()
       
    95     finally:
       
    96         wlock.release()
       
    97     repo._bookmarkcurrent = mark
       
    98 
       
    99 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
       
   100     '''track a line of development with movable markers
       
   101 
       
   102     Bookmarks are pointers to certain commits that move when
       
   103     committing. Bookmarks are local. They can be renamed, copied and
       
   104     deleted. It is possible to use bookmark names in :hg:`merge` and
       
   105     :hg:`update` to merge and update respectively to a given bookmark.
       
   106 
       
   107     You can use :hg:`bookmark NAME` to set a bookmark on the working
       
   108     directory's parent revision with the given name. If you specify
       
   109     a revision using -r REV (where REV may be an existing bookmark),
       
   110     the bookmark is assigned to that revision.
       
   111 
       
   112     Bookmarks can be pushed and pulled between repositories (see :hg:`help
       
   113     push` and :hg:`help pull`). This requires the bookmark extension to be
       
   114     enabled for both the local and remote repositories.
       
   115     '''
       
   116     hexfn = ui.debugflag and hex or short
       
   117     marks = repo._bookmarks
       
   118     cur   = repo.changectx('.').node()
       
   119 
       
   120     if rename:
       
   121         if rename not in marks:
       
   122             raise util.Abort(_("a bookmark of this name does not exist"))
       
   123         if mark in marks and not force:
       
   124             raise util.Abort(_("a bookmark of the same name already exists"))
       
   125         if mark is None:
       
   126             raise util.Abort(_("new bookmark name required"))
       
   127         marks[mark] = marks[rename]
       
   128         del marks[rename]
       
   129         if repo._bookmarkcurrent == rename:
       
   130             setcurrent(repo, mark)
       
   131         write(repo)
       
   132         return
       
   133 
       
   134     if delete:
       
   135         if mark is None:
       
   136             raise util.Abort(_("bookmark name required"))
       
   137         if mark not in marks:
       
   138             raise util.Abort(_("a bookmark of this name does not exist"))
       
   139         if mark == repo._bookmarkcurrent:
       
   140             setcurrent(repo, None)
       
   141         del marks[mark]
       
   142         write(repo)
       
   143         return
       
   144 
       
   145     if mark != None:
       
   146         if "\n" in mark:
       
   147             raise util.Abort(_("bookmark name cannot contain newlines"))
       
   148         mark = mark.strip()
       
   149         if not mark:
       
   150             raise util.Abort(_("bookmark names cannot consist entirely of "
       
   151                                "whitespace"))
       
   152         if mark in marks and not force:
       
   153             raise util.Abort(_("a bookmark of the same name already exists"))
       
   154         if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
       
   155             and not force):
       
   156             raise util.Abort(
       
   157                 _("a bookmark cannot have the name of an existing branch"))
       
   158         if rev:
       
   159             marks[mark] = repo.lookup(rev)
       
   160         else:
       
   161             marks[mark] = repo.changectx('.').node()
       
   162         setcurrent(repo, mark)
       
   163         write(repo)
       
   164         return
       
   165 
       
   166     if mark is None:
       
   167         if rev:
       
   168             raise util.Abort(_("bookmark name required"))
       
   169         if len(marks) == 0:
       
   170             ui.status(_("no bookmarks set\n"))
       
   171         else:
       
   172             for bmark, n in marks.iteritems():
       
   173                 if ui.configbool('bookmarks', 'track.current'):
       
   174                     current = repo._bookmarkcurrent
       
   175                     if bmark == current and n == cur:
       
   176                         prefix, label = '*', 'bookmarks.current'
       
   177                     else:
       
   178                         prefix, label = ' ', ''
       
   179                 else:
       
   180                     if n == cur:
       
   181                         prefix, label = '*', 'bookmarks.current'
       
   182                     else:
       
   183                         prefix, label = ' ', ''
       
   184 
       
   185                 if ui.quiet:
       
   186                     ui.write("%s\n" % bmark, label=label)
       
   187                 else:
       
   188                     ui.write(" %s %-25s %d:%s\n" % (
       
   189                         prefix, bmark, repo.changelog.rev(n), hexfn(n)),
       
   190                         label=label)
       
   191         return
       
   192 
       
   193 def _revstostrip(changelog, node):
       
   194     srev = changelog.rev(node)
       
   195     tostrip = [srev]
       
   196     saveheads = []
       
   197     for r in xrange(srev, len(changelog)):
       
   198         parents = changelog.parentrevs(r)
       
   199         if parents[0] in tostrip or parents[1] in tostrip:
       
   200             tostrip.append(r)
       
   201             if parents[1] != nullrev:
       
   202                 for p in parents:
       
   203                     if p not in tostrip and p > srev:
       
   204                         saveheads.append(p)
       
   205     return [r for r in tostrip if r not in saveheads]
       
   206 
       
   207 def strip(oldstrip, ui, repo, node, backup="all"):
       
   208     """Strip bookmarks if revisions are stripped using
       
   209     the mercurial.strip method. This usually happens during
       
   210     qpush and qpop"""
       
   211     revisions = _revstostrip(repo.changelog, node)
       
   212     marks = repo._bookmarks
       
   213     update = []
       
   214     for mark, n in marks.iteritems():
       
   215         if repo.changelog.rev(n) in revisions:
       
   216             update.append(mark)
       
   217     oldstrip(ui, repo, node, backup)
       
   218     if len(update) > 0:
       
   219         for m in update:
       
   220             marks[m] = repo.changectx('.').node()
       
   221         write(repo)
       
   222 
       
   223 def reposetup(ui, repo):
       
   224     if not repo.local():
       
   225         return
       
   226 
       
   227     class bookmark_repo(repo.__class__):
       
   228 
       
   229         @util.propertycache
       
   230         def _bookmarks(self):
       
   231             '''Parse .hg/bookmarks file and return a dictionary
       
   232 
       
   233             Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
       
   234             in the .hg/bookmarks file.
       
   235             Read the file and return a (name=>nodeid) dictionary
       
   236             '''
       
   237             try:
       
   238                 bookmarks = {}
       
   239                 for line in self.opener('bookmarks'):
       
   240                     sha, refspec = line.strip().split(' ', 1)
       
   241                     bookmarks[refspec] = self.changelog.lookup(sha)
       
   242             except:
       
   243                 pass
       
   244             return bookmarks
       
   245 
       
   246         @util.propertycache
       
   247         def _bookmarkcurrent(self):
       
   248             '''Get the current bookmark
       
   249 
       
   250             If we use gittishsh branches we have a current bookmark that
       
   251             we are on. This function returns the name of the bookmark. It
       
   252             is stored in .hg/bookmarks.current
       
   253             '''
       
   254             mark = None
       
   255             if os.path.exists(self.join('bookmarks.current')):
       
   256                 file = self.opener('bookmarks.current')
       
   257                 # No readline() in posixfile_nt, reading everything is cheap
       
   258                 mark = (file.readlines() or [''])[0]
       
   259                 if mark == '':
       
   260                     mark = None
       
   261                 file.close()
       
   262             return mark
       
   263 
       
   264         def rollback(self, dryrun=False):
       
   265             if os.path.exists(self.join('undo.bookmarks')):
       
   266                 if not dryrun:
       
   267                     util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
       
   268                 elif not os.path.exists(self.sjoin("undo")):
       
   269                     # avoid "no rollback information available" message
       
   270                     return 0
       
   271             return super(bookmark_repo, self).rollback(dryrun)
       
   272 
       
   273         def lookup(self, key):
       
   274             if key in self._bookmarks:
       
   275                 key = self._bookmarks[key]
       
   276             return super(bookmark_repo, self).lookup(key)
       
   277 
       
   278         def _bookmarksupdate(self, parents, node):
       
   279             marks = self._bookmarks
       
   280             update = False
       
   281             if ui.configbool('bookmarks', 'track.current'):
       
   282                 mark = self._bookmarkcurrent
       
   283                 if mark and marks[mark] in parents:
       
   284                     marks[mark] = node
       
   285                     update = True
       
   286             else:
       
   287                 for mark, n in marks.items():
       
   288                     if n in parents:
       
   289                         marks[mark] = node
       
   290                         update = True
       
   291             if update:
       
   292                 write(self)
       
   293 
       
   294         def commitctx(self, ctx, error=False):
       
   295             """Add a revision to the repository and
       
   296             move the bookmark"""
       
   297             wlock = self.wlock() # do both commit and bookmark with lock held
       
   298             try:
       
   299                 node  = super(bookmark_repo, self).commitctx(ctx, error)
       
   300                 if node is None:
       
   301                     return None
       
   302                 parents = self.changelog.parents(node)
       
   303                 if parents[1] == nullid:
       
   304                     parents = (parents[0],)
       
   305 
       
   306                 self._bookmarksupdate(parents, node)
       
   307                 return node
       
   308             finally:
       
   309                 wlock.release()
       
   310 
       
   311         def pull(self, remote, heads=None, force=False):
       
   312             result = super(bookmark_repo, self).pull(remote, heads, force)
       
   313 
       
   314             self.ui.debug("checking for updated bookmarks\n")
       
   315             rb = remote.listkeys('bookmarks')
       
   316             changed = False
       
   317             for k in rb.keys():
       
   318                 if k in self._bookmarks:
       
   319                     nr, nl = rb[k], self._bookmarks[k]
       
   320                     if nr in self:
       
   321                         cr = self[nr]
       
   322                         cl = self[nl]
       
   323                         if cl.rev() >= cr.rev():
       
   324                             continue
       
   325                         if cr in cl.descendants():
       
   326                             self._bookmarks[k] = cr.node()
       
   327                             changed = True
       
   328                             self.ui.status(_("updating bookmark %s\n") % k)
       
   329                         else:
       
   330                             self.ui.warn(_("not updating divergent"
       
   331                                            " bookmark %s\n") % k)
       
   332             if changed:
       
   333                 write(repo)
       
   334 
       
   335             return result
       
   336 
       
   337         def push(self, remote, force=False, revs=None, newbranch=False):
       
   338             result = super(bookmark_repo, self).push(remote, force, revs,
       
   339                                                      newbranch)
       
   340 
       
   341             self.ui.debug("checking for updated bookmarks\n")
       
   342             rb = remote.listkeys('bookmarks')
       
   343             for k in rb.keys():
       
   344                 if k in self._bookmarks:
       
   345                     nr, nl = rb[k], self._bookmarks[k]
       
   346                     if nr in self:
       
   347                         cr = self[nr]
       
   348                         cl = self[nl]
       
   349                         if cl in cr.descendants():
       
   350                             r = remote.pushkey('bookmarks', k, nr, nl)
       
   351                             if r:
       
   352                                 self.ui.status(_("updating bookmark %s\n") % k)
       
   353                             else:
       
   354                                 self.ui.warn(_('updating bookmark %s'
       
   355                                                ' failed!\n') % k)
       
   356 
       
   357             return result
       
   358 
       
   359         def addchangegroup(self, *args, **kwargs):
       
   360             parents = self.dirstate.parents()
       
   361 
       
   362             result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
       
   363             if result > 1:
       
   364                 # We have more heads than before
       
   365                 return result
       
   366             node = self.changelog.tip()
       
   367 
       
   368             self._bookmarksupdate(parents, node)
       
   369             return result
       
   370 
       
   371         def _findtags(self):
       
   372             """Merge bookmarks with normal tags"""
       
   373             (tags, tagtypes) = super(bookmark_repo, self)._findtags()
       
   374             tags.update(self._bookmarks)
       
   375             return (tags, tagtypes)
       
   376 
       
   377         if hasattr(repo, 'invalidate'):
       
   378             def invalidate(self):
       
   379                 super(bookmark_repo, self).invalidate()
       
   380                 for attr in ('_bookmarks', '_bookmarkcurrent'):
       
   381                     if attr in self.__dict__:
       
   382                         delattr(self, attr)
       
   383 
       
   384     repo.__class__ = bookmark_repo
       
   385 
       
   386 def listbookmarks(repo):
       
   387     # We may try to list bookmarks on a repo type that does not
       
   388     # support it (e.g., statichttprepository).
       
   389     if not hasattr(repo, '_bookmarks'):
       
   390         return {}
       
   391 
       
   392     d = {}
       
   393     for k, v in repo._bookmarks.iteritems():
       
   394         d[k] = hex(v)
       
   395     return d
       
   396 
       
   397 def pushbookmark(repo, key, old, new):
       
   398     w = repo.wlock()
       
   399     try:
       
   400         marks = repo._bookmarks
       
   401         if hex(marks.get(key, '')) != old:
       
   402             return False
       
   403         if new == '':
       
   404             del marks[key]
       
   405         else:
       
   406             if new not in repo:
       
   407                 return False
       
   408             marks[key] = repo[new].node()
       
   409         write(repo)
       
   410         return True
       
   411     finally:
       
   412         w.release()
       
   413 
       
   414 def pull(oldpull, ui, repo, source="default", **opts):
       
   415     # translate bookmark args to rev args for actual pull
       
   416     if opts.get('bookmark'):
       
   417         # this is an unpleasant hack as pull will do this internally
       
   418         source, branches = hg.parseurl(ui.expandpath(source),
       
   419                                        opts.get('branch'))
       
   420         other = hg.repository(hg.remoteui(repo, opts), source)
       
   421         rb = other.listkeys('bookmarks')
       
   422 
       
   423         for b in opts['bookmark']:
       
   424             if b not in rb:
       
   425                 raise util.Abort(_('remote bookmark %s not found!') % b)
       
   426             opts.setdefault('rev', []).append(b)
       
   427 
       
   428     result = oldpull(ui, repo, source, **opts)
       
   429 
       
   430     # update specified bookmarks
       
   431     if opts.get('bookmark'):
       
   432         for b in opts['bookmark']:
       
   433             # explicit pull overrides local bookmark if any
       
   434             ui.status(_("importing bookmark %s\n") % b)
       
   435             repo._bookmarks[b] = repo[rb[b]].node()
       
   436         write(repo)
       
   437 
       
   438     return result
       
   439 
       
   440 def push(oldpush, ui, repo, dest=None, **opts):
       
   441     dopush = True
       
   442     if opts.get('bookmark'):
       
   443         dopush = False
       
   444         for b in opts['bookmark']:
       
   445             if b in repo._bookmarks:
       
   446                 dopush = True
       
   447                 opts.setdefault('rev', []).append(b)
       
   448 
       
   449     result = 0
       
   450     if dopush:
       
   451         result = oldpush(ui, repo, dest, **opts)
       
   452 
       
   453     if opts.get('bookmark'):
       
   454         # this is an unpleasant hack as push will do this internally
       
   455         dest = ui.expandpath(dest or 'default-push', dest or 'default')
       
   456         dest, branches = hg.parseurl(dest, opts.get('branch'))
       
   457         other = hg.repository(hg.remoteui(repo, opts), dest)
       
   458         rb = other.listkeys('bookmarks')
       
   459         for b in opts['bookmark']:
       
   460             # explicit push overrides remote bookmark if any
       
   461             if b in repo._bookmarks:
       
   462                 ui.status(_("exporting bookmark %s\n") % b)
       
   463                 new = repo[b].hex()
       
   464             elif b in rb:
       
   465                 ui.status(_("deleting remote bookmark %s\n") % b)
       
   466                 new = '' # delete
       
   467             else:
       
   468                 ui.warn(_('bookmark %s does not exist on the local '
       
   469                           'or remote repository!\n') % b)
       
   470                 return 2
       
   471             old = rb.get(b, '')
       
   472             r = other.pushkey('bookmarks', b, old, new)
       
   473             if not r:
       
   474                 ui.warn(_('updating bookmark %s failed!\n') % b)
       
   475                 if not result:
       
   476                     result = 2
       
   477 
       
   478     return result
       
   479 
       
   480 def diffbookmarks(ui, repo, remote):
       
   481     ui.status(_("searching for changed bookmarks\n"))
       
   482 
       
   483     lmarks = repo.listkeys('bookmarks')
       
   484     rmarks = remote.listkeys('bookmarks')
       
   485 
       
   486     diff = sorted(set(rmarks) - set(lmarks))
       
   487     for k in diff:
       
   488         ui.write("   %-25s %s\n" % (k, rmarks[k][:12]))
       
   489 
       
   490     if len(diff) <= 0:
       
   491         ui.status(_("no changed bookmarks found\n"))
       
   492         return 1
       
   493     return 0
       
   494 
       
   495 def incoming(oldincoming, ui, repo, source="default", **opts):
       
   496     if opts.get('bookmarks'):
       
   497         source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
       
   498         other = hg.repository(hg.remoteui(repo, opts), source)
       
   499         ui.status(_('comparing with %s\n') % url.hidepassword(source))
       
   500         return diffbookmarks(ui, repo, other)
       
   501     else:
       
   502         return oldincoming(ui, repo, source, **opts)
       
   503 
       
   504 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
       
   505     if opts.get('bookmarks'):
       
   506         dest = ui.expandpath(dest or 'default-push', dest or 'default')
       
   507         dest, branches = hg.parseurl(dest, opts.get('branch'))
       
   508         other = hg.repository(hg.remoteui(repo, opts), dest)
       
   509         ui.status(_('comparing with %s\n') % url.hidepassword(dest))
       
   510         return diffbookmarks(ui, other, repo)
       
   511     else:
       
   512         return oldoutgoing(ui, repo, dest, **opts)
       
   513 
       
   514 def uisetup(ui):
       
   515     extensions.wrapfunction(repair, "strip", strip)
       
   516     if ui.configbool('bookmarks', 'track.current'):
       
   517         extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
       
   518 
       
   519     entry = extensions.wrapcommand(commands.table, 'pull', pull)
       
   520     entry[1].append(('B', 'bookmark', [],
       
   521                      _("bookmark to import"),
       
   522                      _('BOOKMARK')))
       
   523     entry = extensions.wrapcommand(commands.table, 'push', push)
       
   524     entry[1].append(('B', 'bookmark', [],
       
   525                      _("bookmark to export"),
       
   526                      _('BOOKMARK')))
       
   527     entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
       
   528     entry[1].append(('B', 'bookmarks', False,
       
   529                      _("compare bookmark")))
       
   530     entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
       
   531     entry[1].append(('B', 'bookmarks', False,
       
   532                      _("compare bookmark")))
       
   533 
       
   534     pushkey.register('bookmarks', pushbookmark, listbookmarks)
       
   535 
       
   536 def updatecurbookmark(orig, ui, repo, *args, **opts):
       
   537     '''Set the current bookmark
       
   538 
       
   539     If the user updates to a bookmark we update the .hg/bookmarks.current
       
   540     file.
       
   541     '''
       
   542     res = orig(ui, repo, *args, **opts)
       
   543     rev = opts['rev']
       
   544     if not rev and len(args) > 0:
       
   545         rev = args[0]
       
   546     setcurrent(repo, rev)
       
   547     return res
       
   548 
       
   549 def bmrevset(repo, subset, x):
       
   550     """``bookmark([name])``
       
   551     The named bookmark or all bookmarks.
       
   552     """
       
   553     # i18n: "bookmark" is a keyword
       
   554     args = revset.getargs(x, 0, 1, _('bookmark takes one or no arguments'))
       
   555     if args:
       
   556         bm = revset.getstring(args[0],
       
   557                               # i18n: "bookmark" is a keyword
       
   558                               _('the argument to bookmark must be a string'))
       
   559         bmrev = listbookmarks(repo).get(bm, None)
       
   560         if bmrev:
       
   561             bmrev = repo.changelog.rev(bin(bmrev))
       
   562         return [r for r in subset if r == bmrev]
       
   563     bms = set([repo.changelog.rev(bin(r)) for r in listbookmarks(repo).values()])
       
   564     return [r for r in subset if r in bms]
       
   565 
       
   566 def extsetup(ui):
       
   567     revset.symbols['bookmark'] = bmrevset
       
   568 
       
   569 cmdtable = {
       
   570     "bookmarks":
       
   571         (bookmark,
       
   572          [('f', 'force', False, _('force')),
       
   573           ('r', 'rev', '', _('revision'), _('REV')),
       
   574           ('d', 'delete', False, _('delete a given bookmark')),
       
   575           ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
       
   576          _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
       
   577 }
       
   578 
       
   579 colortable = {'bookmarks.current': 'green'}
       
   580 
       
   581 # tell hggettext to extract docstrings from these functions:
       
   582 i18nfunctions = [bmrevset]