[PATCH V3] journal: new experimental extension

Yuya Nishihara yuya at tcha.org
Wed Jun 29 09:33:01 EDT 2016


On Fri, 24 Jun 2016 16:30:20 +0100, Martijn Pieters wrote:
> # HG changeset patch
> # User Martijn Pieters <mjpieters at fb.com>
> # Date 1466781125 -3600
> #      Fri Jun 24 16:12:05 2016 +0100
> # Node ID 4653159c0dc01e75ea4f9a1825fa6e511e5bce89
> # Parent  d0ae5b8f80dc115064e66e4ed1dfd848c4f7d1b0
> journal: new experimental extension

I agree the name "journal" is somewhat confusing, but I couldn't think of
better name. So I'm going to queue this.

I found a few nits. I'll fix them inflight if you agree.

And, can you update the wiki page?

https://www.mercurial-scm.org/wiki/ExperimentalExtensionsPlan

> +# storage format version; increment when the format changes
> +storage_version = 0

s/storage_version/storageversion/ per our coding style.

> +def runcommand(orig, lui, repo, cmd, fullargs, *args):
> +    """Track the command line options for recording in the journal"""
> +    journalstorage.recordcommand(*fullargs)
> +    return orig(lui, repo, cmd, fullargs, *args)

Maybe we'll need ui.fullargs or something, but it's beyond the scope of
this patch.

> +    def record(self, namespace, name, oldhashes, newhashes):
> +        """Record a new journal entry
> +
> +        * namespace: an opaque string; this can be used to filter on the type
> +          of recorded entries.
> +        * name: the name defining this entry; for bookmarks, this is the
> +          bookmark name. Can be filtered on when retrieving entries.
> +        * oldhashes and newhashes: each a single binary hash, or a list of
> +          binary hashes. These represent the old and new position of the named
> +          item.
> +
> +        """
> +        if not isinstance(oldhashes, list):
> +            oldhashes = [oldhashes]
> +        if not isinstance(newhashes, list):
> +            newhashes = [newhashes]
> +
> +        entry = journalentry(
> +            util.makedate(), self.user, self.command, namespace, name,
> +            oldhashes, newhashes)
> +
> +        with self.repo.wlock():
> +            version = None
> +            # open file in amend mode to ensure it is created if missing
> +            with self.vfs('journal', mode='a+b', atomictemp=True) as f:
> +                f.seek(0, os.SEEK_SET)
> +                # Read just enough bytes to get a version number (up to 2
> +                # digits plus separator)
> +                version = f.read(3).partition('\0')[0]
> +                if version and version != str(storage_version):
> +                    # different version of the storage. Exit early (and not
> +                    # write anything) if this is not a version we can handle or
> +                    # the file is corrupt. In future, perhaps rotate the file
> +                    # instead?
> +                    self.repo.ui.warn(
> +                        _("unsupported journal file version '%s'\n") % version)
> +                    return
> +                if not version:
> +                    # empty file, write version first
> +                    f.write(str(storage_version) + '\0')
> +                f.seek(0, os.SEEK_END)
> +                f.write(str(entry) + '\0')

[snip]

> +    def __iter__(self):
> +        """Iterate over the storage
> +
> +        Yields journalentry instances for each contained journal record.
> +
> +        """
> +        if not self.vfs.exists('journal'):
> +            return
> +
> +        with self.repo.wlock():
> +            with self.vfs('journal') as f:
> +                raw = f.read()

No need of wlock for reading because the journal file is atomically updated.
I'll remove it.

> +# journal reading
> +# log options that don't make sense for journal
> +_ignore_opts = ('no-merges', 'graph')

s/_ignore_opts/_ignoreopts/

> + at command(
> +    'journal', [
> +        ('c', 'commits', None, 'show commit metadata'),
> +    ] + [opt for opt in commands.logopts if opt[1] not in _ignore_opts],
> +    '[OPTION]... [BOOKMARKNAME]')
> +def journal(ui, repo, *args, **opts):
> +    """show the previous position of bookmarks
> +
> +    The journal is used to see the previous commits of bookmarks. By default
> +    the previous locations for all bookmarks are shown.  Passing a bookmark
> +    name will show all the previous positions of that bookmark.
> +
> +    By default hg journal only shows the commit hash and the command that was
> +    running at that time. -v/--verbose will show the prior hash, the user, and
> +    the time at which it happened.
> +
> +    Use -c/--commits to output log information on each commit hash; at this
> +    point you can use the usual `--patch`, `--git`, `--stat` and `--template`
> +    switches to alter the log output for these.
> +
> +    `hg journal -T json` can be used to produce machine readable output.
> +
> +    """
> +    bookmarkname = None
> +    if args:
> +        bookmarkname = args[0]
> +
> +    fm = ui.formatter('journal', opts)
> +
> +    if opts.get("template") != "json":
> +        if bookmarkname is None:
> +            name = _('all bookmarks')
> +        else:
> +            name = "'%s'" % bookmarkname
> +        ui.status(_("Previous locations of %s:\n") % name)

s/Previous/previous/ for consistency.

> +    limit = cmdutil.loglimit(opts)
> +    entry = None
> +    for count, entry in enumerate(repo.journal.filtered(name=bookmarkname)):
> +        if count == limit:
> +            break
> +        newhashesstr = ','.join([node.short(hash) for hash in entry.newhashes])
> +        oldhashesstr = ','.join([node.short(hash) for hash in entry.oldhashes])
> +
> +        fm.startitem()
> +        fm.condwrite(ui.verbose, 'oldhashes', '%s -> ', oldhashesstr)
> +        fm.write('newhashes', '%s', newhashesstr)
> +        fm.condwrite(ui.verbose, 'user', ' %s', entry.user.ljust(8))
> +
> +        timestring = util.datestr(entry.timestamp, '%Y-%m-%d %H:%M %1%2')
> +        fm.condwrite(ui.verbose, 'date', ' %s', timestring)
> +        fm.write('command', '  %s\n', entry.command)
> +
> +        if opts.get("commits"):
> +            displayer = cmdutil.show_changeset(ui, repo, opts, buffered=False)
> +            for hash in entry.newhashes:
> +                try:
> +                    ctx = repo[hash]
> +                    displayer.show(ctx)
> +                except error.RepoLookupError as e:
> +                    fm.write('repolookuperror', "%s\n\n", str(e))
> +            displayer.close()
> +
> +    fm.end()
> +
> +    if entry is None:
> +        ui.status(_("no recorded locations\n"))

Formatter and templater stuffs will need rework when we settle the output
format of this command. But that won't be easy, and this is an experimental
extension, so I think it's okay to revisit the issue later.


More information about the Mercurial-devel mailing list