Patchwork [6,of,6] largefiles: port wrapped functions to exthelper

login
register
mail settings
Submitter Matt Harbison
Date Dec. 26, 2018, 9:33 p.m.
Message ID <fcd2ee19858f2719ff63.1545860018@Envy>
Download mbox | patch
Permalink /patch/37359/
State Accepted
Headers show

Comments

Matt Harbison - Dec. 26, 2018, 9:33 p.m.
# HG changeset patch
# User Matt Harbison <matt_harbison@yahoo.com>
# Date 1545623823 18000
#      Sun Dec 23 22:57:03 2018 -0500
# Node ID fcd2ee19858f2719ff63fc66be7de577a7aab10d
# Parent  32aa43e038570b844946896376b358cada94f972
largefiles: port wrapped functions to exthelper

Things get interesting in the commit.  I hadn't seen issue6033 on Windows, and
yet it is now reproducible 100% of the time on Windows 10 with this commit.  I
didn't test Linux.  (For comparison, after seeing this issue, I tested on the
parent with --loop, and it failed 5 times out of over 1300 tests.)

The strange thing is that largefiles has nothing to do with that test (it's not
even mentioned there).  It isn't autoloading run amuck- it occurs even if
largefiles is explicitly disabled, and also if the entry in afterhgrcload() is
commented out.  It's also not the import of lfutil- I disabled that by copying
the function into lfs and removing the import, and the problem still occurs.

Experimenting further, it seems that the problem is isolated to 3 entries:
exchange.pushoperation, hg.clone, and cmdutil.revert.  If those decorators are
all commented out, the test passes when run in a loop for awhile.  (Obviously,
some largefiles tests will fail.)  But if any one is commented back in, the test
fails immediately.

I left one method related to wrapping the wire protocol, because it seemed more
natural with the TODO.  Also, exthelper doesn't support wrapping functions from
another extension, only commands in another extension.  I didn't try to figure
out why rebase is both command wrapped and function wrapped.

Patch

diff --git a/hgext/largefiles/__init__.py b/hgext/largefiles/__init__.py
--- a/hgext/largefiles/__init__.py
+++ b/hgext/largefiles/__init__.py
@@ -131,6 +131,7 @@  testedwith = 'ships-with-hg-core'
 eh = exthelper.exthelper()
 eh.merge(lfcommands.eh)
 eh.merge(overrides.eh)
+eh.merge(proto.eh)
 
 eh.configitem('largefiles', 'minsize',
     default=configitems.dynamicdefault,
diff --git a/hgext/largefiles/overrides.py b/hgext/largefiles/overrides.py
--- a/hgext/largefiles/overrides.py
+++ b/hgext/largefiles/overrides.py
@@ -14,19 +14,30 @@  import os
 
 from mercurial.i18n import _
 
+from mercurial.hgweb import (
+    webcommands,
+)
+
 from mercurial import (
     archival,
     cmdutil,
+    copies as copiesmod,
     error,
+    exchange,
     exthelper,
+    filemerge,
     hg,
     logcmdutil,
     match as matchmod,
+    merge,
     pathutil,
     pycompat,
     registrar,
     scmutil,
     smartset,
+    subrepo,
+    upgrade,
+    url as urlmod,
     util,
 )
 
@@ -251,6 +262,7 @@  def removelargefiles(ui, repo, isaddremo
 
 # For overriding mercurial.hgweb.webcommands so that largefiles will
 # appear at their right place in the manifests.
+@eh.wrapfunction(webcommands, 'decodepath')
 def decodepath(orig, path):
     return lfutil.splitstandin(path) or path
 
@@ -266,6 +278,7 @@  def overrideadd(orig, ui, repo, *pats, *
         raise error.Abort(_('--normal cannot be used with --large'))
     return orig(ui, repo, *pats, **opts)
 
+@eh.wrapfunction(cmdutil, 'add')
 def cmdutiladd(orig, ui, repo, matcher, prefix, explicitonly, **opts):
     # The --normal flag short circuits this override
     if opts.get(r'normal'):
@@ -279,6 +292,7 @@  def cmdutiladd(orig, ui, repo, matcher, 
     bad.extend(f for f in lbad)
     return bad
 
+@eh.wrapfunction(cmdutil, 'remove')
 def cmdutilremove(orig, ui, repo, matcher, prefix, after, force, subrepos,
                   dryrun):
     normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
@@ -287,6 +301,7 @@  def cmdutilremove(orig, ui, repo, matche
     return removelargefiles(ui, repo, False, matcher, dryrun, after=after,
                             force=force) or result
 
+@eh.wrapfunction(subrepo.hgsubrepo, 'status')
 def overridestatusfn(orig, repo, rev2, **opts):
     try:
         repo._repo.lfstatus = True
@@ -302,6 +317,7 @@  def overridestatus(orig, ui, repo, *pats
     finally:
         repo.lfstatus = False
 
+@eh.wrapfunction(subrepo.hgsubrepo, 'dirty')
 def overridedirty(orig, repo, ignoreupdate=False, missing=False):
     try:
         repo._repo.lfstatus = True
@@ -454,6 +470,7 @@  def overridedebugstate(orig, ui, repo, *
 # The overridden function filters the unknown files by removing any
 # largefiles. This makes the merge proceed and we can then handle this
 # case further in the overridden calculateupdates function below.
+@eh.wrapfunction(merge, '_checkunknownfile')
 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
     if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
         return False
@@ -485,6 +502,7 @@  def overridecheckunknownfile(origfn, rep
 # Finally, the merge.applyupdates function will then take care of
 # writing the files into the working copy and lfcommands.updatelfiles
 # will update the largefiles.
+@eh.wrapfunction(merge, 'calculateupdates')
 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
                              acceptremote, *args, **kwargs):
     overwrite = force and not branchmerge
@@ -553,6 +571,7 @@  def overridecalculateupdates(origfn, rep
 
     return actions, diverge, renamedelete
 
+@eh.wrapfunction(merge, 'recordupdates')
 def mergerecordupdates(orig, repo, actions, branchmerge):
     if 'lfmr' in actions:
         lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
@@ -568,6 +587,7 @@  def mergerecordupdates(orig, repo, actio
 
 # Override filemerge to prompt the user about how they wish to merge
 # largefiles. This will handle identical edits without prompting the user.
+@eh.wrapfunction(filemerge, '_filemerge')
 def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
                       labels=None):
     if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
@@ -589,6 +609,7 @@  def overridefilemerge(origfn, premerge, 
         repo.wwrite(fcd.path(), fco.data(), fco.flags())
     return True, 0, False
 
+@eh.wrapfunction(copiesmod, 'pathcopies')
 def copiespathcopies(orig, ctx1, ctx2, match=None):
     copies = orig(ctx1, ctx2, match=match)
     updated = {}
@@ -603,6 +624,7 @@  def copiespathcopies(orig, ctx1, ctx2, m
 # checks if the destination largefile already exists. It also keeps a
 # list of copied files so that the largefiles can be copied and the
 # dirstate updated.
+@eh.wrapfunction(cmdutil, 'copy')
 def overridecopy(orig, ui, repo, pats, opts, rename=False):
     # doesn't remove largefile on rename
     if len(pats) < 2:
@@ -748,6 +770,7 @@  def overridecopy(orig, ui, repo, pats, o
 # commits. Update the standins then run the original revert, changing
 # the matcher to hit standins instead of largefiles. Based on the
 # resulting standins update the largefiles.
+@eh.wrapfunction(cmdutil, 'revert')
 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
     # Because we put the standins in a bad state (by updating them)
     # and then return them to a correct state we need to lock to
@@ -857,6 +880,7 @@  def overridepush(orig, ui, repo, *args, 
         opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
     return orig(ui, repo, *args, **kwargs)
 
+@eh.wrapfunction(exchange, 'pushoperation')
 def exchangepushoperation(orig, *args, **kwargs):
     """Override pushoperation constructor and store lfrevs parameter"""
     lfrevs = kwargs.pop(r'lfrevs', None)
@@ -906,6 +930,7 @@  def overrideclone(orig, ui, source, dest
 
     return orig(ui, source, dest, **opts)
 
+@eh.wrapfunction(hg, 'clone')
 def hgclone(orig, ui, opts, *args, **kwargs):
     result = orig(ui, opts, *args, **kwargs)
 
@@ -953,6 +978,7 @@  def overridearchivecmd(orig, ui, repo, d
     finally:
         repo.unfiltered().lfstatus = False
 
+@eh.wrapfunction(webcommands, 'archive')
 def hgwebarchive(orig, web):
     web.repo.lfstatus = True
 
@@ -961,6 +987,7 @@  def hgwebarchive(orig, web):
     finally:
         web.repo.lfstatus = False
 
+@eh.wrapfunction(archival, 'archive')
 def overridearchive(orig, repo, dest, node, kind, decode=True, match=None,
             prefix='', mtime=None, subrepos=None):
     # For some reason setting repo.lfstatus in hgwebarchive only changes the
@@ -1029,6 +1056,7 @@  def overridearchive(orig, repo, dest, no
 
     archiver.done()
 
+@eh.wrapfunction(subrepo.hgsubrepo, 'archive')
 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
     lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
     if not lfenabled or not repo._repo.lfstatus:
@@ -1083,6 +1111,7 @@  def hgsubrepoarchive(orig, repo, archive
 # standin until a commit. cmdutil.bailifchanged() raises an exception
 # if the repo has uncommitted changes. Wrap it to also check if
 # largefiles were changed. This is used by bisect, backout and fetch.
+@eh.wrapfunction(cmdutil, 'bailifchanged')
 def overridebailifchanged(orig, repo, *args, **kwargs):
     orig(repo, *args, **kwargs)
     repo.lfstatus = True
@@ -1091,6 +1120,7 @@  def overridebailifchanged(orig, repo, *a
     if s.modified or s.added or s.removed or s.deleted:
         raise error.Abort(_('uncommitted changes'))
 
+@eh.wrapfunction(cmdutil, 'postcommitstatus')
 def postcommitstatus(orig, repo, *args, **kwargs):
     repo.lfstatus = True
     try:
@@ -1098,6 +1128,7 @@  def postcommitstatus(orig, repo, *args, 
     finally:
         repo.lfstatus = False
 
+@eh.wrapfunction(cmdutil, 'forget')
 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly, dryrun,
                   interactive):
     normalmatcher = composenormalfilematcher(match, repo[None].manifest())
@@ -1244,6 +1275,7 @@  def overridesummary(orig, ui, repo, *pat
     finally:
         repo.lfstatus = False
 
+@eh.wrapfunction(scmutil, 'addremove')
 def scmutiladdremove(orig, repo, matcher, prefix, opts=None):
     if opts is None:
         opts = {}
@@ -1420,6 +1452,7 @@  def overridecat(orig, ui, repo, file1, *
         err = 0
     return err
 
+@eh.wrapfunction(merge, 'update')
 def mergeupdate(orig, repo, node, branchmerge, force,
                 *args, **kwargs):
     matcher = kwargs.get(r'matcher', None)
@@ -1497,6 +1530,7 @@  def mergeupdate(orig, repo, node, branch
 
         return result
 
+@eh.wrapfunction(scmutil, 'marktouched')
 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
     result = orig(repo, files, *args, **kwargs)
 
@@ -1511,6 +1545,8 @@  def scmutilmarktouched(orig, repo, files
 
     return result
 
+@eh.wrapfunction(upgrade, 'preservedrequirements')
+@eh.wrapfunction(upgrade, 'supporteddestrequirements')
 def upgraderequirements(orig, repo):
     reqs = orig(repo)
     if 'largefiles' in repo.requirements:
@@ -1518,6 +1554,8 @@  def upgraderequirements(orig, repo):
     return reqs
 
 _lfscheme = 'largefile://'
+
+@eh.wrapfunction(urlmod, 'open')
 def openlargefile(orig, ui, url_, data=None):
     if url_.startswith(_lfscheme):
         if data:
diff --git a/hgext/largefiles/proto.py b/hgext/largefiles/proto.py
--- a/hgext/largefiles/proto.py
+++ b/hgext/largefiles/proto.py
@@ -11,10 +11,12 @@  from mercurial.i18n import _
 
 from mercurial import (
     error,
+    exthelper,
     httppeer,
     util,
     wireprototypes,
     wireprotov1peer,
+    wireprotov1server,
 )
 
 from . import (
@@ -28,6 +30,8 @@  LARGEFILES_REQUIRED_MSG = ('\nThis repos
                            '\n\nPlease enable it in your Mercurial config '
                            'file.\n')
 
+eh = exthelper.exthelper()
+
 # these will all be replaced by largefiles.uisetup
 ssholdcallstream = None
 httpoldcallstream = None
@@ -162,6 +166,7 @@  def wirereposetup(ui, repo):
     repo.__class__ = lfileswirerepository
 
 # advertise the largefiles=serve capability
+@eh.wrapfunction(wireprotov1server, '_capabilities')
 def _capabilities(orig, repo, proto):
     '''announce largefile server capability'''
     caps = orig(repo, proto)
diff --git a/hgext/largefiles/uisetup.py b/hgext/largefiles/uisetup.py
--- a/hgext/largefiles/uisetup.py
+++ b/hgext/largefiles/uisetup.py
@@ -9,27 +9,11 @@ 
 '''setup for largefiles extension: uisetup'''
 from __future__ import absolute_import
 
-from mercurial.i18n import _
-
-from mercurial.hgweb import (
-    webcommands,
-)
-
 from mercurial import (
-    archival,
     cmdutil,
-    copies,
-    exchange,
     extensions,
-    filemerge,
-    hg,
     httppeer,
-    merge,
-    scmutil,
     sshpeer,
-    subrepo,
-    upgrade,
-    url,
     wireprotov1server,
 )
 
@@ -39,67 +23,10 @@  from . import (
 )
 
 def uisetup(ui):
-    # Disable auto-status for some commands which assume that all
-    # files in the result are under Mercurial's control
-
-    # The scmutil function is called both by the (trivial) addremove command,
-    # and in the process of handling commit -A (issue3542)
-    extensions.wrapfunction(scmutil, 'addremove', overrides.scmutiladdremove)
-    extensions.wrapfunction(cmdutil, 'add', overrides.cmdutiladd)
-    extensions.wrapfunction(cmdutil, 'remove', overrides.cmdutilremove)
-    extensions.wrapfunction(cmdutil, 'forget', overrides.cmdutilforget)
-
-    extensions.wrapfunction(copies, 'pathcopies', overrides.copiespathcopies)
-
-    extensions.wrapfunction(upgrade, 'preservedrequirements',
-                            overrides.upgraderequirements)
-
-    extensions.wrapfunction(upgrade, 'supporteddestrequirements',
-                            overrides.upgraderequirements)
-
-    # Subrepos call status function
-    extensions.wrapfunction(subrepo.hgsubrepo, 'status',
-                            overrides.overridestatusfn)
 
     cmdutil.outgoinghooks.add('largefiles', overrides.outgoinghook)
     cmdutil.summaryremotehooks.add('largefiles', overrides.summaryremotehook)
 
-    extensions.wrapfunction(exchange, 'pushoperation',
-                            overrides.exchangepushoperation)
-
-    extensions.wrapfunction(hg, 'clone', overrides.hgclone)
-
-    extensions.wrapfunction(merge, '_checkunknownfile',
-                            overrides.overridecheckunknownfile)
-    extensions.wrapfunction(merge, 'calculateupdates',
-                            overrides.overridecalculateupdates)
-    extensions.wrapfunction(merge, 'recordupdates',
-                            overrides.mergerecordupdates)
-    extensions.wrapfunction(merge, 'update', overrides.mergeupdate)
-    extensions.wrapfunction(filemerge, '_filemerge',
-                            overrides.overridefilemerge)
-    extensions.wrapfunction(cmdutil, 'copy', overrides.overridecopy)
-
-    # Summary calls dirty on the subrepos
-    extensions.wrapfunction(subrepo.hgsubrepo, 'dirty', overrides.overridedirty)
-
-    extensions.wrapfunction(cmdutil, 'revert', overrides.overriderevert)
-
-    extensions.wrapfunction(archival, 'archive', overrides.overridearchive)
-    extensions.wrapfunction(subrepo.hgsubrepo, 'archive',
-                            overrides.hgsubrepoarchive)
-    extensions.wrapfunction(webcommands, 'archive', overrides.hgwebarchive)
-    extensions.wrapfunction(cmdutil, 'bailifchanged',
-                            overrides.overridebailifchanged)
-
-    extensions.wrapfunction(cmdutil, 'postcommitstatus',
-                            overrides.postcommitstatus)
-    extensions.wrapfunction(scmutil, 'marktouched',
-                            overrides.scmutilmarktouched)
-
-    extensions.wrapfunction(url, 'open',
-                            overrides.openlargefile)
-
     # create the new wireproto commands ...
     wireprotov1server.wireprotocommand('putlfile', 'sha', permission='push')(
         proto.putlfile)
@@ -110,16 +37,10 @@  def uisetup(ui):
     wireprotov1server.wireprotocommand('lheads', '', permission='pull')(
         wireprotov1server.heads)
 
-    # ... and wrap some existing ones
     extensions.wrapfunction(wireprotov1server.commands['heads'], 'func',
                             proto.heads)
     # TODO also wrap wireproto.commandsv2 once heads is implemented there.
 
-    extensions.wrapfunction(webcommands, 'decodepath', overrides.decodepath)
-
-    extensions.wrapfunction(wireprotov1server, '_capabilities',
-                            proto._capabilities)
-
     # can't do this in reposetup because it needs to have happened before
     # wirerepo.__init__ is called
     proto.ssholdcallstream = sshpeer.sshv1peer._callstream
@@ -130,5 +51,6 @@  def uisetup(ui):
     # override some extensions' stuff as well
     for name, module in extensions.extensions():
         if name == 'rebase':
+            # TODO: teach exthelper to handle this
             extensions.wrapfunction(module, 'rebase',
                                     overrides.overriderebase)