--- 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)
--- 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