[PATCH 1 of 3 V2] journal: add dirstate tracking

FUJIWARA Katsunori foozy at lares.dti.ne.jp
Mon Jul 4 17:28:01 EDT 2016


At Mon, 04 Jul 2016 15:35:56 +0100,
Martijn Pieters wrote:
> 
> # HG changeset patch
> # User Martijn Pieters <mjpieters at fb.com>
> # Date 1467642813 -3600
> #      Mon Jul 04 15:33:33 2016 +0100
> # Node ID 05a4e9561e5398502acb7d2329a1ad9adff774cf
> # Parent  53b7fc7cc2bbbcd1c344ce50519a868cac7ed9ac
> 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.
> 
> diff --git a/hgext/journal.py b/hgext/journal.py
> --- a/hgext/journal.py
> +++ b/hgext/journal.py
> @@ -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)
>  

Wrapping "dirstate._writedirstate()" might track intermediate changing
parents while automated commit like backout, graft, import, rebase and
so on.

Recording such intermediate status depends on:

  - whether changing parents while automated commit occurs inside
    transaction or not

    - if so (e.g. import):

      - whether external hook is invoked or not inside transaction scope

        - if so, intermediate commit writes changes (= changing
          parents) out at external hook invocation

        - otherwise, writing changes out is delayed until transaction
          closing

    - otherwise (e.g. graft):

      actions below immediately write intermediate changes out.

      - intermediate commit
      - external hook invocation after internal updating

(see also https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
for detail about dirstate and transaction)

What would you think about appearance of such intermediate change in
journal records ?

IMHO, from the point of view of end user, comparison between "parents"
before and after each command invocation seems enough resolution for
recording changes.

> +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._filename is always the actual dirstate file;
> +            # backups for a transactions, shelving or the repo journal use a
> +            # suffix or prefix, _filename never changes.
> +            with dirstate._opener(dirstate._filename) 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:

Comparison between "parents" in memory and in ".hg/dirstate" might
imply unexpected record for automated commit inside transaction,
because changes inside transaction are written into ".hg/dirstate.pending".

  - this comparison always concludes that "parents" is changed, even
    if it isn't changed after previous recording

  - "parents" stored in ".hg/dirstate" is always used as the source of
    changing parents

    e.g. if automated commit implies A -> I1 -> I2 -> I3 -> B commits,
    this records (A -> I1), (A -> I2), (A -> I3), (A -> B).

    (if external hook is invoked)



> +            # 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 +163,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 +193,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 +232,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 +245,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 +298,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 +323,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])
> @@ -275,6 +353,7 @@
>          fm.condwrite(ui.verbose, 'oldhashes', '%s -> ', oldhashesstr)
>          fm.write('newhashes', '%s', newhashesstr)
>          fm.condwrite(ui.verbose, 'user', ' %s', entry.user.ljust(8))
> +        fm.condwrite(opts.get('all'), 'name', '  %s', entry.name.ljust(8))
>  
>          timestring = util.datestr(entry.timestamp, '%Y-%m-%d %H:%M %1%2')
>          fm.condwrite(ui.verbose, 'date', ' %s', timestring)
> diff --git a/tests/test-journal.t b/tests/test-journal.t
> --- a/tests/test-journal.t
> +++ b/tests/test-journal.t
> @@ -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
> +  $ 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 '.':
>    1e6c11564562  up
> -  cb9a9f314b8b  book -f bar
> -  1e6c11564562  book -r tip bar
> -  $ hg journal --limit 2
> -  previous locations of all bookmarks:
> -  1e6c11564562  book -r tip baz
> -  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,27 @@
>    $ 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
>  
> -  $ 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 --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 +147,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 +162,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
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel at mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel

----------------------------------------------------------------------
[FUJIWARA Katsunori]                             foozy at lares.dti.ne.jp


More information about the Mercurial-devel mailing list