Patchwork [6,of,6,RFC] extension: `dummy` and composition with `lfs`

login
register
mail settings
Submitter Remi Chaintron
Date Oct. 27, 2016, 3:04 p.m.
Message ID <c7dc98eb24cd40e46e73.1477580644@remi-mbp2.dhcp.thefacebook.com>
Download mbox | patch
Permalink /patch/17206/
State Deferred
Headers show

Comments

Remi Chaintron - Oct. 27, 2016, 3:04 p.m.
# HG changeset patch
# User Remi Chaintron <remi@fb.com>
# Date 1477579974 -3600
#      Thu Oct 27 15:52:54 2016 +0100
# Branch stable
# Node ID c7dc98eb24cd40e46e73dea275b4c5b245155efb
# Parent  202b2a6872d3b3ffb1f373910c4eadffda8f727a
[RFC] extension: `dummy` and composition with `lfs`

This is a proof of concept for the composition of non-reflexive operations on
filelogs using extensions relying on the flagprocessor design.
Augie Fackler - Nov. 9, 2016, 5:17 p.m.
On Thu, Oct 27, 2016 at 04:04:04PM +0100, Remi Chaintron wrote:
> # HG changeset patch
> # User Remi Chaintron <remi@fb.com>
> # Date 1477579974 -3600
> #      Thu Oct 27 15:52:54 2016 +0100
> # Branch stable
> # Node ID c7dc98eb24cd40e46e73dea275b4c5b245155efb
> # Parent  202b2a6872d3b3ffb1f373910c4eadffda8f727a
> [RFC] extension: `dummy` and composition with `lfs`
>
> This is a proof of concept for the composition of non-reflexive operations on
> filelogs using extensions relying on the flagprocessor design.

Could this be tested using censor instead?

>
> diff --git a/hgext/dummy.py b/hgext/dummy.py
> new file mode 100644
> --- /dev/null
> +++ b/hgext/dummy.py
> @@ -0,0 +1,83 @@
> +# coding=UTF-8
> +
> +from __future__ import absolute_import
> +
> +from mercurial import (
> +    cmdutil,
> +    commands,
> +    extensions,
> +    filelog,
> +    revlog,
> +)
> +
> +def wrappedread(self, text):
> +    return text[6:], True
> +
> +def getfilelogcontent(self, text):
> +    return text, False
> +
> +extensionflag = revlog.REVIDX_ISDUMMY
> +transformmap = {
> +    extensionflag: wrappedread
> +}
> +
> +def revision(orig, self, nodeorrev, _df=None):
> +    self.flagprocessor.register(transformmap)
> +    try:
> +        return orig(self, nodeorrev, _df=_df)
> +    finally:
> +        self.flagprocessor.unregister(transformmap)
> +
> +
> +def addrevision(orig, self, text, transaction, link, p1, p2, cachedelta=None,
> +                node=None, flags=revlog.REVLOG_DEFAULT_FLAGS):
> +    self.flagprocessor.register(transformmap)
> +    try:
> +      if self._peek_isdummy(text):
> +          if node is None:
> +             node = revlog.hash(text, p1, p2)
> +          text = 'DUMMY:%s' % (text)
> +          flags |= extensionflag
> +      return orig(self, text, transaction, link, p1, p2, cachedelta=cachedelta,
> +                  node=node, flags=flags)
> +    finally:
> +        self.flagprocessor.unregister(transformmap)
> +
> +def addgroup(orig, self, cg, linkmapper, transaction, addrevisioncb=None):
> +    self.flagprocessor.register(transformmap)
> +    try:
> +        return orig(self, cg, linkmapper, transaction,
> +                    addrevisioncb=addrevisioncb)
> +    finally:
> +        self.flagprocessor.unregister(transformmap)
> +
> +def debugdata(orig, ui, repo, file_, rev=None, **opts):
> +    transformmap[extensionflag] = getfilelogcontent
> +    orig(ui, repo, file_, rev=rev, **opts)
> +    transformmap[extensionflag] = wrappedread
> +
> +def push(orig, ui, repo, dest=None, **opts):
> +    transformmap[extensionflag] = getfilelogcontent
> +    orig(ui, repo, dest=dest, **opts)
> +    transformmap[extensionflag] = wrappedread
> +
> +def pull(orig, ui, repo, source="default", **opts):
> +    transformmap[extensionflag] = getfilelogcontent
> +    orig(ui, repo, source=source, **opts)
> +    transformmap[extensionflag] = wrappedread
> +
> +def _peek_isdummy(orig, self, text):
> +    return 'dummy' in text
> +
> +def extsetup(ui):
> +    wrapfunction = extensions.wrapfunction
> +    wrapcommand = extensions.wrapcommand
> +
> +    wrapfunction(filelog.filelog, 'revision', revision)
> +    wrapfunction(filelog.filelog, 'addrevision', addrevision)
> +    wrapfunction(filelog.filelog, 'addgroup', addgroup)
> +    wrapfunction(filelog.filelog, '_peek_isdummy', _peek_isdummy)
> +
> +    wrapcommand(commands.table, 'debugdata', debugdata)
> +    wrapcommand(commands.table, 'push', push)
> +    wrapcommand(commands.table, 'pull', pull)
> diff --git a/mercurial/revlog.py b/mercurial/revlog.py
> --- a/mercurial/revlog.py
> +++ b/mercurial/revlog.py
> @@ -55,8 +55,9 @@
>  # revlog index flags
>  REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
>  REVIDX_ISLARGEFILE = (1 << 14)
> +REVIDX_ISDUMMY = (1 << 13)
>  REVIDX_DEFAULT_FLAGS = 0
> -REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED | REVIDX_ISLARGEFILE
> +REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED | REVIDX_ISLARGEFILE | REVIDX_ISDUMMY
>
>  # max size of revlog with inline data
>  _maxinline = 131072
> @@ -97,6 +98,7 @@
>          self.flagsbypriority = [
>              REVIDX_ISCENSORED,
>              REVIDX_ISLARGEFILE,
> +            REVIDX_ISDUMMY,
>          ]
>          self.transformmap = {}
>          self.revlogobject = revlogobject
> @@ -1775,6 +1777,9 @@
>      def islargefile(self, rev):
>          return False
>
> +    def _peek_isdummy(self, text):
> +        return False
> +
>      def _peek_iscensored(self, baserev, delta, flush):
>          """Quickly check if a delta produces a censored revision."""
>          return False
> diff --git a/tests/test-lfs.t b/tests/test-lfs.t
> --- a/tests/test-lfs.t
> +++ b/tests/test-lfs.t
> @@ -19,6 +19,7 @@
>    $ cat >> .hg/hgrc <<EOF
>    > [extensions]
>    > lfs=$TESTDIR/../hgext/lfs/
> +  > dummy=
>    > [lfs]
>    > threshold=1000B
>    > blobstore=cache/localblobstore
> @@ -49,19 +50,43 @@
>    version https://git-lfs.github.com/spec/v1
>    oid sha256:1228f8759b018cca5b58d3e5d1740d0b827f06cecf8868fd17f35a87ef8aacf6
>    size 1501
> -
> -# Check the contents of the file are fetched from the blobstore when requested
> -  $ hg cat -r . largefile
> +  $ hg cat --traceback -r . largefile
>    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
>
> -# Push the blobs to the server
> -  $ hg push
> +# Commit a dummy file (will take the dummy flag)
> +  $ echo 'dummy' > dummyfile
> +  $ hg commit -Aqm 'add dummy file'
> +
> +# Check the dummy extension adds metadata on write
> +  $ hg debugdata dummyfile 0
> +  DUMMY:dummy
> +
> +# Check the extension removes the metadata on read
> +  $ hg cat -r . dummyfile
> +  dummy
> +
> +# Commit a lfs + dummy triggering file
> +  $ echo dummyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA > dummylargefile
> +  $ hg commit -Aqm 'add dummy large file'
> +
> +# Check the filelog contains metadata (as dummy has priority on lfs)
> +  $ hg debugdata dummylargefile 0
> +  version https://git-lfs.github.com/spec/v1
> +  oid sha256:22a3bdbccf152a786e35ca856dfef423c022968fc42f80f1c2eff8e8b116c37d
> +  size 1512
> +
> +# Check the file contents are restored on read
> +  $ hg cat -r . dummylargefile
> +  dummyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
> +
> +# Push changes to the server
> +  $ hg push --traceback
>    pushing to $TESTTMP/server (glob)
>    searching for changes
>    adding changesets
>    adding manifests
>    adding file changes
> -  added 2 changesets with 2 changes to 2 files
> +  added 4 changesets with 4 changes to 4 files
>
>  # Initialize new client (not cloning) and setup extension
>    $ cd ../
> @@ -76,6 +101,7 @@
>    > default = $TESTTMP/server
>    > [extensions]
>    > lfs=$TESTDIR/../hgext/lfs/
> +  > dummy=
>    > [lfs]
>    > threshold=1000B
>    > blobstore=cache/localblobstore
> @@ -88,7 +114,7 @@
>    adding changesets
>    adding manifests
>    adding file changes
> -  added 2 changesets with 2 changes to 2 files
> +  added 4 changesets with 4 changes to 4 files
>    (run 'hg update' to get a working copy)
>
>  # Check the blobstore is not yet populated
> @@ -97,7 +123,7 @@
>
>  # Update to the last revision containing the large file
>    $ hg update
> -  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
> +  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
>
>  # Check the filelog contents actually contain metadata
>    $ hg debugdata largefile 0
> @@ -110,6 +136,8 @@
>    .hg/cache/localblobstore
>    .hg/cache/localblobstore/12
>    .hg/cache/localblobstore/12/28f8759b018cca5b58d3e5d1740d0b827f06cecf8868fd17f35a87ef8aacf6
> +  .hg/cache/localblobstore/22
> +  .hg/cache/localblobstore/22/a3bdbccf152a786e35ca856dfef423c022968fc42f80f1c2eff8e8b116c37d
>
>  # Check the contents of the file are fetched from blobstore when requested
>    $ hg cat -r . largefile
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Remi Chaintron - Nov. 16, 2016, 2:12 p.m.
On 11/9/16, 5:17 PM, "Augie Fackler" <raf@durin42.com> wrote:

    On Thu, Oct 27, 2016 at 04:04:04PM +0100, Remi Chaintron wrote:
    > # HG changeset patch

    > # User Remi Chaintron <remi@fb.com>

    > # Date 1477579974 -3600

    > #      Thu Oct 27 15:52:54 2016 +0100

    > # Branch stable

    > # Node ID c7dc98eb24cd40e46e73dea275b4c5b245155efb

    > # Parent  202b2a6872d3b3ffb1f373910c4eadffda8f727a

    > [RFC] extension: `dummy` and composition with `lfs`

    >

    > This is a proof of concept for the composition of non-reflexive operations on

    > filelogs using extensions relying on the flagprocessor design.

    
    Could this be tested using censor instead?
    
I currently have a prototype to move various bits and pieces from censor related core code into the extension, but given the extensive rework and redesign involved, I found it more applicable to provide an easier proof of concept instead.
I’d love to get a proposal up for review in regards to moving censor to the extension, but I’m not positive I will have the time to get everything in order to make it happen before the end of the year.

    >

    > diff --git a/hgext/dummy.py b/hgext/dummy.py

    > new file mode 100644

    > --- /dev/null

    > +++ b/hgext/dummy.py

    > @@ -0,0 +1,83 @@

    > +# coding=UTF-8

    > +

    > +from __future__ import absolute_import

    > +

    > +from mercurial import (

    > +    cmdutil,

    > +    commands,

    > +    extensions,

    > +    filelog,

    > +    revlog,

    > +)

    > +

    > +def wrappedread(self, text):

    > +    return text[6:], True

    > +

    > +def getfilelogcontent(self, text):

    > +    return text, False

    > +

    > +extensionflag = revlog.REVIDX_ISDUMMY

    > +transformmap = {

    > +    extensionflag: wrappedread

    > +}

    > +

    > +def revision(orig, self, nodeorrev, _df=None):

    > +    self.flagprocessor.register(transformmap)

    > +    try:

    > +        return orig(self, nodeorrev, _df=_df)

    > +    finally:

    > +        self.flagprocessor.unregister(transformmap)

    > +

    > +

    > +def addrevision(orig, self, text, transaction, link, p1, p2, cachedelta=None,

    > +                node=None, flags=revlog.REVLOG_DEFAULT_FLAGS):

    > +    self.flagprocessor.register(transformmap)

    > +    try:

    > +      if self._peek_isdummy(text):

    > +          if node is None:

    > +             node = revlog.hash(text, p1, p2)

    > +          text = 'DUMMY:%s' % (text)

    > +          flags |= extensionflag

    > +      return orig(self, text, transaction, link, p1, p2, cachedelta=cachedelta,

    > +                  node=node, flags=flags)

    > +    finally:

    > +        self.flagprocessor.unregister(transformmap)

    > +

    > +def addgroup(orig, self, cg, linkmapper, transaction, addrevisioncb=None):

    > +    self.flagprocessor.register(transformmap)

    > +    try:

    > +        return orig(self, cg, linkmapper, transaction,

    > +                    addrevisioncb=addrevisioncb)

    > +    finally:

    > +        self.flagprocessor.unregister(transformmap)

    > +

    > +def debugdata(orig, ui, repo, file_, rev=None, **opts):

    > +    transformmap[extensionflag] = getfilelogcontent

    > +    orig(ui, repo, file_, rev=rev, **opts)

    > +    transformmap[extensionflag] = wrappedread

    > +

    > +def push(orig, ui, repo, dest=None, **opts):

    > +    transformmap[extensionflag] = getfilelogcontent

    > +    orig(ui, repo, dest=dest, **opts)

    > +    transformmap[extensionflag] = wrappedread

    > +

    > +def pull(orig, ui, repo, source="default", **opts):

    > +    transformmap[extensionflag] = getfilelogcontent

    > +    orig(ui, repo, source=source, **opts)

    > +    transformmap[extensionflag] = wrappedread

    > +

    > +def _peek_isdummy(orig, self, text):

    > +    return 'dummy' in text

    > +

    > +def extsetup(ui):

    > +    wrapfunction = extensions.wrapfunction

    > +    wrapcommand = extensions.wrapcommand

    > +

    > +    wrapfunction(filelog.filelog, 'revision', revision)

    > +    wrapfunction(filelog.filelog, 'addrevision', addrevision)

    > +    wrapfunction(filelog.filelog, 'addgroup', addgroup)

    > +    wrapfunction(filelog.filelog, '_peek_isdummy', _peek_isdummy)

    > +

    > +    wrapcommand(commands.table, 'debugdata', debugdata)

    > +    wrapcommand(commands.table, 'push', push)

    > +    wrapcommand(commands.table, 'pull', pull)

    > diff --git a/mercurial/revlog.py b/mercurial/revlog.py

    > --- a/mercurial/revlog.py

    > +++ b/mercurial/revlog.py

    > @@ -55,8 +55,9 @@

    >  # revlog index flags

    >  REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified

    >  REVIDX_ISLARGEFILE = (1 << 14)

    > +REVIDX_ISDUMMY = (1 << 13)

    >  REVIDX_DEFAULT_FLAGS = 0

    > -REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED | REVIDX_ISLARGEFILE

    > +REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED | REVIDX_ISLARGEFILE | REVIDX_ISDUMMY

    >

    >  # max size of revlog with inline data

    >  _maxinline = 131072

    > @@ -97,6 +98,7 @@

    >          self.flagsbypriority = [

    >              REVIDX_ISCENSORED,

    >              REVIDX_ISLARGEFILE,

    > +            REVIDX_ISDUMMY,

    >          ]

    >          self.transformmap = {}

    >          self.revlogobject = revlogobject

    > @@ -1775,6 +1777,9 @@

    >      def islargefile(self, rev):

    >          return False

    >

    > +    def _peek_isdummy(self, text):

    > +        return False

    > +

    >      def _peek_iscensored(self, baserev, delta, flush):

    >          """Quickly check if a delta produces a censored revision."""

    >          return False

    > diff --git a/tests/test-lfs.t b/tests/test-lfs.t

    > --- a/tests/test-lfs.t

    > +++ b/tests/test-lfs.t

    > @@ -19,6 +19,7 @@

    >    $ cat >> .hg/hgrc <<EOF

    >    > [extensions]

    >    > lfs=$TESTDIR/../hgext/lfs/

    > +  > dummy=

    >    > [lfs]

    >    > threshold=1000B

    >    > blobstore=cache/localblobstore

    > @@ -49,19 +50,43 @@

    >    version https://git-lfs.github.com/spec/v1

    >    oid sha256:1228f8759b018cca5b58d3e5d1740d0b827f06cecf8868fd17f35a87ef8aacf6

    >    size 1501

    > -

    > -# Check the contents of the file are fetched from the blobstore when requested

    > -  $ hg cat -r . largefile

    > +  $ hg cat --traceback -r . largefile

    >    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

    >

    > -# Push the blobs to the server

    > -  $ hg push

    > +# Commit a dummy file (will take the dummy flag)

    > +  $ echo 'dummy' > dummyfile

    > +  $ hg commit -Aqm 'add dummy file'

    > +

    > +# Check the dummy extension adds metadata on write

    > +  $ hg debugdata dummyfile 0

    > +  DUMMY:dummy

    > +

    > +# Check the extension removes the metadata on read

    > +  $ hg cat -r . dummyfile

    > +  dummy

    > +

    > +# Commit a lfs + dummy triggering file

    > +  $ echo dummyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA > dummylargefile

    > +  $ hg commit -Aqm 'add dummy large file'

    > +

    > +# Check the filelog contains metadata (as dummy has priority on lfs)

    > +  $ hg debugdata dummylargefile 0

    > +  version https://git-lfs.github.com/spec/v1

    > +  oid sha256:22a3bdbccf152a786e35ca856dfef423c022968fc42f80f1c2eff8e8b116c37d

    > +  size 1512

    > +

    > +# Check the file contents are restored on read

    > +  $ hg cat -r . dummylargefile

    > +  dummyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

    > +

    > +# Push changes to the server

    > +  $ hg push --traceback

    >    pushing to $TESTTMP/server (glob)

    >    searching for changes

    >    adding changesets

    >    adding manifests

    >    adding file changes

    > -  added 2 changesets with 2 changes to 2 files

    > +  added 4 changesets with 4 changes to 4 files

    >

    >  # Initialize new client (not cloning) and setup extension

    >    $ cd ../

    > @@ -76,6 +101,7 @@

    >    > default = $TESTTMP/server

    >    > [extensions]

    >    > lfs=$TESTDIR/../hgext/lfs/

    > +  > dummy=

    >    > [lfs]

    >    > threshold=1000B

    >    > blobstore=cache/localblobstore

    > @@ -88,7 +114,7 @@

    >    adding changesets

    >    adding manifests

    >    adding file changes

    > -  added 2 changesets with 2 changes to 2 files

    > +  added 4 changesets with 4 changes to 4 files

    >    (run 'hg update' to get a working copy)

    >

    >  # Check the blobstore is not yet populated

    > @@ -97,7 +123,7 @@

    >

    >  # Update to the last revision containing the large file

    >    $ hg update

    > -  2 files updated, 0 files merged, 0 files removed, 0 files unresolved

    > +  4 files updated, 0 files merged, 0 files removed, 0 files unresolved

    >

    >  # Check the filelog contents actually contain metadata

    >    $ hg debugdata largefile 0

    > @@ -110,6 +136,8 @@

    >    .hg/cache/localblobstore

    >    .hg/cache/localblobstore/12

    >    .hg/cache/localblobstore/12/28f8759b018cca5b58d3e5d1740d0b827f06cecf8868fd17f35a87ef8aacf6

    > +  .hg/cache/localblobstore/22

    > +  .hg/cache/localblobstore/22/a3bdbccf152a786e35ca856dfef423c022968fc42f80f1c2eff8e8b116c37d

    >

    >  # Check the contents of the file are fetched from blobstore when requested

    >    $ hg cat -r . largefile

    > _______________________________________________

    > Mercurial-devel mailing list

    > Mercurial-devel@mercurial-scm.org

    > https://urldefense.proofpoint.com/v2/url?u=https-3A__www.mercurial-2Dscm.org_mailman_listinfo_mercurial-2Ddevel&d=DQIFAg&c=5VD0RTtNlTh3ycd41b3MUw&r=RjAECLzUoKvzn49UTozsUg&m=5RyPgFuzIirEktiCbTiLncwaRvGm6WhEv21cqfaP5-o&s=40rBjU8_W8S68vg_RCGfz4bO3GsV4mSj784C1r4Jk70&e=
Augie Fackler - Nov. 16, 2016, 3:44 p.m.
> On Nov 16, 2016, at 09:12, Rémi Chaintron <remi@fb.com> wrote:
> 
> On 11/9/16, 5:17 PM, "Augie Fackler" <raf@durin42.com> wrote:
> 
>>    On Thu, Oct 27, 2016 at 04:04:04PM +0100, Remi Chaintron wrote:
>>> # HG changeset patch
>>> # User Remi Chaintron <remi@fb.com>
>>> # Date 1477579974 -3600
>>> #      Thu Oct 27 15:52:54 2016 +0100
>>> # Branch stable
>>> # Node ID c7dc98eb24cd40e46e73dea275b4c5b245155efb
>>> # Parent  202b2a6872d3b3ffb1f373910c4eadffda8f727a
>>> [RFC] extension: `dummy` and composition with `lfs`
>>> 
>>> This is a proof of concept for the composition of non-reflexive operations on
>>> filelogs using extensions relying on the flagprocessor design.
>> 
>>    Could this be tested using censor instead?
> 
> I currently have a prototype to move various bits and pieces from censor related core code into the extension, but given the extensive rework and redesign involved, I found it more applicable to provide an easier proof of concept instead.
> I’d love to get a proposal up for review in regards to moving censor to the extension, but I’m not positive I will have the time to get everything in order to make it happen before the end of the year.

Does that mean that with this series as-is, censor doesn't use the new code paths? Have you explored how much refactoring has to happen in censor to make censor use the new system?

I'd rather that the first-party consumer of this infrastructure used it right away, rather than it being a TODO unless it's extremely onerous.
Remi Chaintron - Nov. 16, 2016, 5:18 p.m.
On 11/16/16, 3:44 PM, "Augie Fackler" <raf@durin42.com> wrote:
>> On Nov 16, 2016, at 09:12, Rémi Chaintron <remi@fb.com> wrote:

>>

>> On 11/9/16, 5:17 PM, "Augie Fackler" <raf@durin42.com> wrote:

>>

>>>    On Thu, Oct 27, 2016 at 04:04:04PM +0100, Remi Chaintron wrote:

>>>> # HG changeset patch

>>>> # User Remi Chaintron <remi@fb.com>

>>>> # Date 1477579974 -3600

>>>> #      Thu Oct 27 15:52:54 2016 +0100

>>>> # Branch stable

>>>> # Node ID c7dc98eb24cd40e46e73dea275b4c5b245155efb

>>>> # Parent  202b2a6872d3b3ffb1f373910c4eadffda8f727a

>>>> [RFC] extension: `dummy` and composition with `lfs`

>>>>

>>>> This is a proof of concept for the composition of non-reflexive operations on

>>>> filelogs using extensions relying on the flagprocessor design.

>>>

>>>    Could this be tested using censor instead?

>>

>> I currently have a prototype to move various bits and pieces from censor related core code into the extension, but given the extensive rework and redesign involved, I found it more applicable to provide an easier proof of concept instead.

>> I’d love to get a proposal up for review in regards to moving censor to the extension, but I’m not positive I will have the time to get everything in order to make it happen before the end of the year.

>

> Does that mean that with this series as-is, censor doesn't use the new code paths? Have you explored how much refactoring has to happen in censor to make censor use the new system?

>

> I'd rather that the first-party consumer of this infrastructure used it right away, rather than it being a TODO unless it's extremely onerous.


Precisions following VC discussion with Augie:
- Moving censor to use the new flagprocessor design allows for cleaning all censor related filelog code, and much of the revlog censor related code. There are however a number of issues that I didn’t have time to push through on completely cleaning censor code out of core and will take another stab at it when possible.
- Censor can be considered a special case of this new design as it doesn’t allow two-way transformations. Once a node has been censored, there’s no going back and reading such a node should fail, irrelevant of the place of censor in the stack of transforms to be applied.
- This is a proof a concept for the flagprocessor design handling multiple non-commutative operations on the filelog contents.

Patch

diff --git a/hgext/dummy.py b/hgext/dummy.py
new file mode 100644
--- /dev/null
+++ b/hgext/dummy.py
@@ -0,0 +1,83 @@ 
+# coding=UTF-8
+
+from __future__ import absolute_import
+
+from mercurial import (
+    cmdutil,
+    commands,
+    extensions,
+    filelog,
+    revlog,
+)
+
+def wrappedread(self, text):
+    return text[6:], True
+
+def getfilelogcontent(self, text):
+    return text, False
+
+extensionflag = revlog.REVIDX_ISDUMMY
+transformmap = {
+    extensionflag: wrappedread
+}
+
+def revision(orig, self, nodeorrev, _df=None):
+    self.flagprocessor.register(transformmap)
+    try:
+        return orig(self, nodeorrev, _df=_df)
+    finally:
+        self.flagprocessor.unregister(transformmap)
+
+
+def addrevision(orig, self, text, transaction, link, p1, p2, cachedelta=None,
+                node=None, flags=revlog.REVLOG_DEFAULT_FLAGS):
+    self.flagprocessor.register(transformmap)
+    try:
+      if self._peek_isdummy(text):
+          if node is None:
+             node = revlog.hash(text, p1, p2)
+          text = 'DUMMY:%s' % (text)
+          flags |= extensionflag
+      return orig(self, text, transaction, link, p1, p2, cachedelta=cachedelta,
+                  node=node, flags=flags)
+    finally:
+        self.flagprocessor.unregister(transformmap)
+
+def addgroup(orig, self, cg, linkmapper, transaction, addrevisioncb=None):
+    self.flagprocessor.register(transformmap)
+    try:
+        return orig(self, cg, linkmapper, transaction,
+                    addrevisioncb=addrevisioncb)
+    finally:
+        self.flagprocessor.unregister(transformmap)
+
+def debugdata(orig, ui, repo, file_, rev=None, **opts):
+    transformmap[extensionflag] = getfilelogcontent
+    orig(ui, repo, file_, rev=rev, **opts)
+    transformmap[extensionflag] = wrappedread
+
+def push(orig, ui, repo, dest=None, **opts):
+    transformmap[extensionflag] = getfilelogcontent
+    orig(ui, repo, dest=dest, **opts)
+    transformmap[extensionflag] = wrappedread
+
+def pull(orig, ui, repo, source="default", **opts):
+    transformmap[extensionflag] = getfilelogcontent
+    orig(ui, repo, source=source, **opts)
+    transformmap[extensionflag] = wrappedread
+
+def _peek_isdummy(orig, self, text):
+    return 'dummy' in text
+
+def extsetup(ui):
+    wrapfunction = extensions.wrapfunction
+    wrapcommand = extensions.wrapcommand
+
+    wrapfunction(filelog.filelog, 'revision', revision)
+    wrapfunction(filelog.filelog, 'addrevision', addrevision)
+    wrapfunction(filelog.filelog, 'addgroup', addgroup)
+    wrapfunction(filelog.filelog, '_peek_isdummy', _peek_isdummy)
+
+    wrapcommand(commands.table, 'debugdata', debugdata)
+    wrapcommand(commands.table, 'push', push)
+    wrapcommand(commands.table, 'pull', pull)
diff --git a/mercurial/revlog.py b/mercurial/revlog.py
--- a/mercurial/revlog.py
+++ b/mercurial/revlog.py
@@ -55,8 +55,9 @@ 
 # revlog index flags
 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
 REVIDX_ISLARGEFILE = (1 << 14)
+REVIDX_ISDUMMY = (1 << 13)
 REVIDX_DEFAULT_FLAGS = 0
-REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED | REVIDX_ISLARGEFILE
+REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED | REVIDX_ISLARGEFILE | REVIDX_ISDUMMY
 
 # max size of revlog with inline data
 _maxinline = 131072
@@ -97,6 +98,7 @@ 
         self.flagsbypriority = [
             REVIDX_ISCENSORED,
             REVIDX_ISLARGEFILE,
+            REVIDX_ISDUMMY,
         ]
         self.transformmap = {}
         self.revlogobject = revlogobject
@@ -1775,6 +1777,9 @@ 
     def islargefile(self, rev):
         return False
 
+    def _peek_isdummy(self, text):
+        return False
+
     def _peek_iscensored(self, baserev, delta, flush):
         """Quickly check if a delta produces a censored revision."""
         return False
diff --git a/tests/test-lfs.t b/tests/test-lfs.t
--- a/tests/test-lfs.t
+++ b/tests/test-lfs.t
@@ -19,6 +19,7 @@ 
   $ cat >> .hg/hgrc <<EOF
   > [extensions]
   > lfs=$TESTDIR/../hgext/lfs/
+  > dummy=
   > [lfs]
   > threshold=1000B
   > blobstore=cache/localblobstore
@@ -49,19 +50,43 @@ 
   version https://git-lfs.github.com/spec/v1
   oid sha256:1228f8759b018cca5b58d3e5d1740d0b827f06cecf8868fd17f35a87ef8aacf6
   size 1501
-
-# Check the contents of the file are fetched from the blobstore when requested
-  $ hg cat -r . largefile
+  $ hg cat --traceback -r . largefile
   AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 
-# Push the blobs to the server
-  $ hg push
+# Commit a dummy file (will take the dummy flag)
+  $ echo 'dummy' > dummyfile
+  $ hg commit -Aqm 'add dummy file'
+
+# Check the dummy extension adds metadata on write
+  $ hg debugdata dummyfile 0
+  DUMMY:dummy
+
+# Check the extension removes the metadata on read
+  $ hg cat -r . dummyfile
+  dummy
+
+# Commit a lfs + dummy triggering file
+  $ echo dummyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA > dummylargefile
+  $ hg commit -Aqm 'add dummy large file'
+
+# Check the filelog contains metadata (as dummy has priority on lfs)
+  $ hg debugdata dummylargefile 0
+  version https://git-lfs.github.com/spec/v1
+  oid sha256:22a3bdbccf152a786e35ca856dfef423c022968fc42f80f1c2eff8e8b116c37d
+  size 1512
+
+# Check the file contents are restored on read
+  $ hg cat -r . dummylargefile
+  dummyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+
+# Push changes to the server
+  $ hg push --traceback
   pushing to $TESTTMP/server (glob)
   searching for changes
   adding changesets
   adding manifests
   adding file changes
-  added 2 changesets with 2 changes to 2 files
+  added 4 changesets with 4 changes to 4 files
 
 # Initialize new client (not cloning) and setup extension
   $ cd ../
@@ -76,6 +101,7 @@ 
   > default = $TESTTMP/server
   > [extensions]
   > lfs=$TESTDIR/../hgext/lfs/
+  > dummy=
   > [lfs]
   > threshold=1000B
   > blobstore=cache/localblobstore
@@ -88,7 +114,7 @@ 
   adding changesets
   adding manifests
   adding file changes
-  added 2 changesets with 2 changes to 2 files
+  added 4 changesets with 4 changes to 4 files
   (run 'hg update' to get a working copy)
 
 # Check the blobstore is not yet populated
@@ -97,7 +123,7 @@ 
 
 # Update to the last revision containing the large file
   $ hg update
-  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 # Check the filelog contents actually contain metadata
   $ hg debugdata largefile 0
@@ -110,6 +136,8 @@ 
   .hg/cache/localblobstore
   .hg/cache/localblobstore/12
   .hg/cache/localblobstore/12/28f8759b018cca5b58d3e5d1740d0b827f06cecf8868fd17f35a87ef8aacf6
+  .hg/cache/localblobstore/22
+  .hg/cache/localblobstore/22/a3bdbccf152a786e35ca856dfef423c022968fc42f80f1c2eff8e8b116c37d
 
 # Check the contents of the file are fetched from blobstore when requested
   $ hg cat -r . largefile