# HG changeset patch # User Martijn Pieters # Date 1468240764 -3600 # Node ID 8361131b4768a10c0fef220ffe79008da3558e37 # Parent be68a44450416581227c43216f07ad8a71e550bc journal: add dirstate tracking Note that now the default action for `hg journal` is to list the working copy history, not all bookmarks. In its place is the `--all` switch which lists all name changes recorded, including the name for which the change was recorded on each line. Locking is switched to using a dedicated lock to avoid issues with the dirstate being written during wlock unlocking (you can't re-lock during that process). diff -r be68a4445041 -r 8361131b4768 hgext/journal.py --- a/hgext/journal.py Mon Jul 11 08:54:13 2016 -0500 +++ b/hgext/journal.py Mon Jul 11 13:39:24 2016 +0100 @@ -15,6 +15,7 @@ import collections import os +import weakref from mercurial.i18n import _ @@ -22,9 +23,12 @@ bookmarks, cmdutil, commands, + dirstate, dispatch, error, extensions, + localrepo, + lock, node, util, ) @@ -43,11 +47,16 @@ # namespaces bookmarktype = 'bookmark' +wdirparenttype = 'wdirparent' # Journal recording, register hooks and storage object def extsetup(ui): extensions.wrapfunction(dispatch, 'runcommand', runcommand) extensions.wrapfunction(bookmarks.bmstore, '_write', recordbookmarks) + extensions.wrapfunction( + dirstate.dirstate, '_writedirstate', recorddirstateparents) + extensions.wrapfunction( + localrepo.localrepository.dirstate, 'func', wrapdirstate) def reposetup(ui, repo): if repo.local(): @@ -58,6 +67,42 @@ journalstorage.recordcommand(*fullargs) return orig(lui, repo, cmd, fullargs, *args) +# hooks to record dirstate changes +def wrapdirstate(orig, repo): + """Make journal storage available to the dirstate object""" + dirstate = orig(repo) + if util.safehasattr(repo, 'journal'): + dirstate.journalstorage = repo.journal + return dirstate + +def recorddirstateparents(orig, dirstate, dirstatefp): + """Records all dirstate parent changes in the journal.""" + if util.safehasattr(dirstate, 'journalstorage'): + old = [node.nullid, node.nullid] + nodesize = len(node.nullid) + try: + # The only source for the old state is in the dirstate file still + # on disk; the in-memory dirstate object only contains the new + # state. dirstate._opendirstatefile() switches beteen .hg/dirstate + # and .hg/dirstate.pending depending on the transaction state. + with dirstate._opendirstatefile() as fp: + state = fp.read(2 * nodesize) + if len(state) == 2 * nodesize: + old = [state[:nodesize], state[nodesize:]] + except IOError: + pass + + new = dirstate.parents() + if old != new: + # only record two hashes if there was a merge + oldhashes = old[:1] if old[1] == node.nullid else old + newhashes = new[:1] if new[1] == node.nullid else new + dirstate.journalstorage.record( + wdirparenttype, '.', oldhashes, newhashes) + + return orig(dirstate, dirstatefp) + +# hooks to record bookmark changes (both local and remote) def recordbookmarks(orig, store, fp): """Records all bookmark changes in the journal.""" repo = store._repo @@ -117,12 +162,17 @@ The file format starts with an integer version, delimited by a NUL. + This storage uses a dedicated lock; this makes it easier to avoid issues + with adding entries that added when the regular wlock is unlocked (e.g. + the dirstate). + """ _currentcommand = () + _lockref = None def __init__(self, repo): - self.repo = repo self.user = util.getuser() + self.ui = repo.ui self.vfs = repo.vfs # track the current command for recording in journal entries @@ -142,6 +192,24 @@ # with a non-local repo (cloning for example). cls._currentcommand = fullargs + def jlock(self): + """Create a lock for the journal file""" + if self._lockref and self._lockref(): + raise error.Abort(_('journal lock does not support nesting')) + desc = _('journal of %s') % self.vfs.base + try: + l = lock.lock(self.vfs, 'journal.lock', 0, desc=desc) + except error.LockHeld as inst: + self.ui.warn( + _("waiting for lock on %s held by %r\n") % (desc, inst.locker)) + # default to 600 seconds timeout + l = lock.lock( + self.vfs, 'journal.lock', + int(self.ui.config("ui", "timeout", "600")), desc=desc) + self.ui.warn(_("got lock after %s seconds\n") % l.delay) + self._lockref = weakref.ref(l) + return l + def record(self, namespace, name, oldhashes, newhashes): """Record a new journal entry @@ -163,7 +231,7 @@ util.makedate(), self.user, self.command, namespace, name, oldhashes, newhashes) - with self.repo.wlock(): + with self.jlock(): 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: @@ -176,7 +244,7 @@ # 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( + self.ui.warn( _("unsupported journal file version '%s'\n") % version) return if not version: @@ -229,15 +297,19 @@ _ignoreopts = ('no-merges', 'graph') @command( 'journal', [ + ('', 'all', None, 'show history for all names'), ('c', 'commits', None, 'show commit metadata'), ] + [opt for opt in commands.logopts if opt[1] not in _ignoreopts], '[OPTION]... [BOOKMARKNAME]') def journal(ui, repo, *args, **opts): - """show the previous position of bookmarks + """show the previous position of bookmarks and the working copy - 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. + The journal is used to see the previous commits that bookmarks and the + working copy pointed to. By default the previous locations for the working + copy. Passing a bookmark name will show all the previous positions of + that bookmark. Use the --all switch to show previous locations for all + bookmarks and the working copy; each line will then include the bookmark + name, or '.' for the working copy, as well. 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 @@ -250,22 +322,27 @@ `hg journal -T json` can be used to produce machine readable output. """ - bookmarkname = None + name = '.' + if opts.get('all'): + if args: + raise error.Abort( + _("You can't combine --all and filtering on a name")) + name = None if args: - bookmarkname = args[0] + name = args[0] fm = ui.formatter('journal', opts) if opts.get("template") != "json": - if bookmarkname is None: - name = _('all bookmarks') + if name is None: + displayname = _('the working copy and bookmarks') else: - name = "'%s'" % bookmarkname - ui.status(_("previous locations of %s:\n") % name) + displayname = "'%s'" % name + ui.status(_("previous locations of %s:\n") % displayname) limit = cmdutil.loglimit(opts) entry = None - for count, entry in enumerate(repo.journal.filtered(name=bookmarkname)): + for count, entry in enumerate(repo.journal.filtered(name=name)): if count == limit: break newhashesstr = ','.join([node.short(hash) for hash in entry.newhashes]) @@ -274,7 +351,8 @@ fm.startitem() fm.condwrite(ui.verbose, 'oldhashes', '%s -> ', oldhashesstr) fm.write('newhashes', '%s', newhashesstr) - fm.condwrite(ui.verbose, 'user', ' %s', entry.user.ljust(8)) + fm.condwrite(ui.verbose, 'user', ' %-8s', entry.user) + fm.condwrite(opts.get('all'), 'name', ' %-8s', entry.name) timestring = util.datestr(entry.timestamp, '%Y-%m-%d %H:%M %1%2') fm.condwrite(ui.verbose, 'date', ' %s', timestring) diff -r be68a4445041 -r 8361131b4768 tests/test-journal.t --- a/tests/test-journal.t Mon Jul 11 08:54:13 2016 -0500 +++ b/tests/test-journal.t Mon Jul 11 13:39:24 2016 +0100 @@ -32,22 +32,37 @@ $ hg init repo $ cd repo - $ echo a > a - $ hg commit -Aqm a - $ echo b > a - $ hg commit -Aqm b - $ hg up 0 - 1 files updated, 0 files merged, 0 files removed, 0 files unresolved Test empty journal $ hg journal - previous locations of all bookmarks: + previous locations of '.': no recorded locations $ hg journal foo previous locations of 'foo': no recorded locations +Test that working copy changes are tracked + + $ echo a > a + $ hg commit -Aqm a + $ hg journal + previous locations of '.': + cb9a9f314b8b commit -Aqm a + $ echo b > a + $ hg commit -Aqm b + $ hg journal + previous locations of '.': + 1e6c11564562 commit -Aqm b + cb9a9f314b8b commit -Aqm a + $ hg up 0 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg journal + previous locations of '.': + cb9a9f314b8b up 0 + 1e6c11564562 commit -Aqm b + cb9a9f314b8b commit -Aqm a + Test that bookmarks are tracked $ hg book -r tip bar @@ -68,22 +83,32 @@ cb9a9f314b8b book -f bar 1e6c11564562 book -r tip bar -Test that you can list all bookmarks as well as limit the list or filter on them +Test that bookmarks and working copy tracking is not mixed + + $ hg journal + previous locations of '.': + 1e6c11564562 up + cb9a9f314b8b up 0 + 1e6c11564562 commit -Aqm b + cb9a9f314b8b commit -Aqm a + +Test that you can list all entries as well as limit the list or filter on them $ hg book -r tip baz - $ hg journal - previous locations of all bookmarks: - 1e6c11564562 book -r tip baz - 1e6c11564562 up - cb9a9f314b8b book -f bar - 1e6c11564562 book -r tip bar + $ hg journal --all + previous locations of the working copy and bookmarks: + 1e6c11564562 baz book -r tip baz + 1e6c11564562 bar up + 1e6c11564562 . up + cb9a9f314b8b bar book -f bar + 1e6c11564562 bar book -r tip bar + cb9a9f314b8b . up 0 + 1e6c11564562 . commit -Aqm b + cb9a9f314b8b . commit -Aqm a $ hg journal --limit 2 - previous locations of all bookmarks: - 1e6c11564562 book -r tip baz + previous locations of '.': 1e6c11564562 up - $ hg journal baz - previous locations of 'baz': - 1e6c11564562 book -r tip baz + cb9a9f314b8b up 0 $ hg journal bar previous locations of 'bar': 1e6c11564562 up @@ -92,26 +117,62 @@ $ hg journal foo previous locations of 'foo': no recorded locations + $ hg journal . + previous locations of '.': + 1e6c11564562 up + cb9a9f314b8b up 0 + 1e6c11564562 commit -Aqm b + cb9a9f314b8b commit -Aqm a -Test that verbose and commit output work +Test that verbose, JSON and commit output work - $ hg journal --verbose - previous locations of all bookmarks: - 000000000000 -> 1e6c11564562 foobar 1970-01-01 00:00 +0000 book -r tip baz - cb9a9f314b8b -> 1e6c11564562 foobar 1970-01-01 00:00 +0000 up - 1e6c11564562 -> cb9a9f314b8b foobar 1970-01-01 00:00 +0000 book -f bar - 000000000000 -> 1e6c11564562 foobar 1970-01-01 00:00 +0000 book -r tip bar + $ hg journal --verbose --all + previous locations of the working copy and bookmarks: + 000000000000 -> 1e6c11564562 foobar baz 1970-01-01 00:00 +0000 book -r tip baz + cb9a9f314b8b -> 1e6c11564562 foobar bar 1970-01-01 00:00 +0000 up + cb9a9f314b8b -> 1e6c11564562 foobar . 1970-01-01 00:00 +0000 up + 1e6c11564562 -> cb9a9f314b8b foobar bar 1970-01-01 00:00 +0000 book -f bar + 000000000000 -> 1e6c11564562 foobar bar 1970-01-01 00:00 +0000 book -r tip bar + 1e6c11564562 -> cb9a9f314b8b foobar . 1970-01-01 00:00 +0000 up 0 + cb9a9f314b8b -> 1e6c11564562 foobar . 1970-01-01 00:00 +0000 commit -Aqm b + 000000000000 -> cb9a9f314b8b foobar . 1970-01-01 00:00 +0000 commit -Aqm a + $ hg journal --verbose -Tjson + [ + { + "command": "up", + "date": "1970-01-01 00:00 +0000", + "name": ".", + "newhashes": "1e6c11564562", + "oldhashes": "cb9a9f314b8b", + "user": "foobar" + }, + { + "command": "up 0", + "date": "1970-01-01 00:00 +0000", + "name": ".", + "newhashes": "cb9a9f314b8b", + "oldhashes": "1e6c11564562", + "user": "foobar" + }, + { + "command": "commit -Aqm b", + "date": "1970-01-01 00:00 +0000", + "name": ".", + "newhashes": "1e6c11564562", + "oldhashes": "cb9a9f314b8b", + "user": "foobar" + }, + { + "command": "commit -Aqm a", + "date": "1970-01-01 00:00 +0000", + "name": ".", + "newhashes": "cb9a9f314b8b", + "oldhashes": "000000000000", + "user": "foobar" + } + ] $ hg journal --commit - previous locations of all bookmarks: - 1e6c11564562 book -r tip baz - changeset: 1:1e6c11564562 - bookmark: bar - bookmark: baz - tag: tip - user: test - date: Thu Jan 01 00:00:00 1970 +0000 - summary: b - + previous locations of '.': 1e6c11564562 up changeset: 1:1e6c11564562 bookmark: bar @@ -121,13 +182,13 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: b - cb9a9f314b8b book -f bar + cb9a9f314b8b up 0 changeset: 0:cb9a9f314b8b user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: a - 1e6c11564562 book -r tip bar + 1e6c11564562 commit -Aqm b changeset: 1:1e6c11564562 bookmark: bar bookmark: baz @@ -136,12 +197,18 @@ date: Thu Jan 01 00:00:00 1970 +0000 summary: b + cb9a9f314b8b commit -Aqm a + changeset: 0:cb9a9f314b8b + user: test + date: Thu Jan 01 00:00:00 1970 +0000 + summary: a + Test for behaviour on unexpected storage version information $ printf '42\0' > .hg/journal $ hg journal - previous locations of all bookmarks: + previous locations of '.': abort: unknown journal file version '42' [255] $ hg book -r tip doomed