[PATCH V3] journal: new experimental extension

Martijn Pieters mj at zopatista.com
Wed Jun 29 14:02:24 EDT 2016


On 29 June 2016 at 14:33, Yuya Nishihara <yuya at tcha.org> wrote:
> 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.

That's fine, thanks!

> And, can you update the wiki page?
>
> https://www.mercurial-scm.org/wiki/ExperimentalExtensionsPlan

Done

>> +# 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.

Ah, better without if not needed.

>> +# 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.

I've included that in the experimental extension notes on the wiki.

Thanks!

-- 
Martijn Pieters


More information about the Mercurial-devel mailing list