[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