comparison hgext/largefiles/overrides.py @ 41062:0a7f582f6f1f

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.
author Matt Harbison <matt_harbison@yahoo.com>
date Sun, 23 Dec 2018 22:57:03 -0500
parents 98681293c890
children f2601cbce209
comparison
equal deleted inserted replaced
41061:98681293c890 41062:0a7f582f6f1f
12 import copy 12 import copy
13 import os 13 import os
14 14
15 from mercurial.i18n import _ 15 from mercurial.i18n import _
16 16
17 from mercurial.hgweb import (
18 webcommands,
19 )
20
17 from mercurial import ( 21 from mercurial import (
18 archival, 22 archival,
19 cmdutil, 23 cmdutil,
24 copies as copiesmod,
20 error, 25 error,
26 exchange,
21 exthelper, 27 exthelper,
28 filemerge,
22 hg, 29 hg,
23 logcmdutil, 30 logcmdutil,
24 match as matchmod, 31 match as matchmod,
32 merge,
25 pathutil, 33 pathutil,
26 pycompat, 34 pycompat,
27 registrar, 35 registrar,
28 scmutil, 36 scmutil,
29 smartset, 37 smartset,
38 subrepo,
39 upgrade,
40 url as urlmod,
30 util, 41 util,
31 ) 42 )
32 43
33 from . import ( 44 from . import (
34 lfcommands, 45 lfcommands,
249 260
250 return result 261 return result
251 262
252 # For overriding mercurial.hgweb.webcommands so that largefiles will 263 # For overriding mercurial.hgweb.webcommands so that largefiles will
253 # appear at their right place in the manifests. 264 # appear at their right place in the manifests.
265 @eh.wrapfunction(webcommands, 'decodepath')
254 def decodepath(orig, path): 266 def decodepath(orig, path):
255 return lfutil.splitstandin(path) or path 267 return lfutil.splitstandin(path) or path
256 268
257 # -- Wrappers: modify existing commands -------------------------------- 269 # -- Wrappers: modify existing commands --------------------------------
258 270
264 def overrideadd(orig, ui, repo, *pats, **opts): 276 def overrideadd(orig, ui, repo, *pats, **opts):
265 if opts.get(r'normal') and opts.get(r'large'): 277 if opts.get(r'normal') and opts.get(r'large'):
266 raise error.Abort(_('--normal cannot be used with --large')) 278 raise error.Abort(_('--normal cannot be used with --large'))
267 return orig(ui, repo, *pats, **opts) 279 return orig(ui, repo, *pats, **opts)
268 280
281 @eh.wrapfunction(cmdutil, 'add')
269 def cmdutiladd(orig, ui, repo, matcher, prefix, explicitonly, **opts): 282 def cmdutiladd(orig, ui, repo, matcher, prefix, explicitonly, **opts):
270 # The --normal flag short circuits this override 283 # The --normal flag short circuits this override
271 if opts.get(r'normal'): 284 if opts.get(r'normal'):
272 return orig(ui, repo, matcher, prefix, explicitonly, **opts) 285 return orig(ui, repo, matcher, prefix, explicitonly, **opts)
273 286
277 bad = orig(ui, repo, normalmatcher, prefix, explicitonly, **opts) 290 bad = orig(ui, repo, normalmatcher, prefix, explicitonly, **opts)
278 291
279 bad.extend(f for f in lbad) 292 bad.extend(f for f in lbad)
280 return bad 293 return bad
281 294
295 @eh.wrapfunction(cmdutil, 'remove')
282 def cmdutilremove(orig, ui, repo, matcher, prefix, after, force, subrepos, 296 def cmdutilremove(orig, ui, repo, matcher, prefix, after, force, subrepos,
283 dryrun): 297 dryrun):
284 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest()) 298 normalmatcher = composenormalfilematcher(matcher, repo[None].manifest())
285 result = orig(ui, repo, normalmatcher, prefix, after, force, subrepos, 299 result = orig(ui, repo, normalmatcher, prefix, after, force, subrepos,
286 dryrun) 300 dryrun)
287 return removelargefiles(ui, repo, False, matcher, dryrun, after=after, 301 return removelargefiles(ui, repo, False, matcher, dryrun, after=after,
288 force=force) or result 302 force=force) or result
289 303
304 @eh.wrapfunction(subrepo.hgsubrepo, 'status')
290 def overridestatusfn(orig, repo, rev2, **opts): 305 def overridestatusfn(orig, repo, rev2, **opts):
291 try: 306 try:
292 repo._repo.lfstatus = True 307 repo._repo.lfstatus = True
293 return orig(repo, rev2, **opts) 308 return orig(repo, rev2, **opts)
294 finally: 309 finally:
300 repo.lfstatus = True 315 repo.lfstatus = True
301 return orig(ui, repo, *pats, **opts) 316 return orig(ui, repo, *pats, **opts)
302 finally: 317 finally:
303 repo.lfstatus = False 318 repo.lfstatus = False
304 319
320 @eh.wrapfunction(subrepo.hgsubrepo, 'dirty')
305 def overridedirty(orig, repo, ignoreupdate=False, missing=False): 321 def overridedirty(orig, repo, ignoreupdate=False, missing=False):
306 try: 322 try:
307 repo._repo.lfstatus = True 323 repo._repo.lfstatus = True
308 return orig(repo, ignoreupdate=ignoreupdate, missing=missing) 324 return orig(repo, ignoreupdate=ignoreupdate, missing=missing)
309 finally: 325 finally:
452 # in a file 'foo' if we already have a largefile with the same name. 468 # in a file 'foo' if we already have a largefile with the same name.
453 # 469 #
454 # The overridden function filters the unknown files by removing any 470 # The overridden function filters the unknown files by removing any
455 # largefiles. This makes the merge proceed and we can then handle this 471 # largefiles. This makes the merge proceed and we can then handle this
456 # case further in the overridden calculateupdates function below. 472 # case further in the overridden calculateupdates function below.
473 @eh.wrapfunction(merge, '_checkunknownfile')
457 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None): 474 def overridecheckunknownfile(origfn, repo, wctx, mctx, f, f2=None):
458 if lfutil.standin(repo.dirstate.normalize(f)) in wctx: 475 if lfutil.standin(repo.dirstate.normalize(f)) in wctx:
459 return False 476 return False
460 return origfn(repo, wctx, mctx, f, f2) 477 return origfn(repo, wctx, mctx, f, f2)
461 478
483 # presumably changed on purpose. 500 # presumably changed on purpose.
484 # 501 #
485 # Finally, the merge.applyupdates function will then take care of 502 # Finally, the merge.applyupdates function will then take care of
486 # writing the files into the working copy and lfcommands.updatelfiles 503 # writing the files into the working copy and lfcommands.updatelfiles
487 # will update the largefiles. 504 # will update the largefiles.
505 @eh.wrapfunction(merge, 'calculateupdates')
488 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force, 506 def overridecalculateupdates(origfn, repo, p1, p2, pas, branchmerge, force,
489 acceptremote, *args, **kwargs): 507 acceptremote, *args, **kwargs):
490 overwrite = force and not branchmerge 508 overwrite = force and not branchmerge
491 actions, diverge, renamedelete = origfn( 509 actions, diverge, renamedelete = origfn(
492 repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs) 510 repo, p1, p2, pas, branchmerge, force, acceptremote, *args, **kwargs)
551 actions[lfile] = ('g', largs, 'replaces standin') 569 actions[lfile] = ('g', largs, 'replaces standin')
552 actions[standin] = ('r', None, 'replaced by non-standin') 570 actions[standin] = ('r', None, 'replaced by non-standin')
553 571
554 return actions, diverge, renamedelete 572 return actions, diverge, renamedelete
555 573
574 @eh.wrapfunction(merge, 'recordupdates')
556 def mergerecordupdates(orig, repo, actions, branchmerge): 575 def mergerecordupdates(orig, repo, actions, branchmerge):
557 if 'lfmr' in actions: 576 if 'lfmr' in actions:
558 lfdirstate = lfutil.openlfdirstate(repo.ui, repo) 577 lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
559 for lfile, args, msg in actions['lfmr']: 578 for lfile, args, msg in actions['lfmr']:
560 # this should be executed before 'orig', to execute 'remove' 579 # this should be executed before 'orig', to execute 'remove'
566 585
567 return orig(repo, actions, branchmerge) 586 return orig(repo, actions, branchmerge)
568 587
569 # Override filemerge to prompt the user about how they wish to merge 588 # Override filemerge to prompt the user about how they wish to merge
570 # largefiles. This will handle identical edits without prompting the user. 589 # largefiles. This will handle identical edits without prompting the user.
590 @eh.wrapfunction(filemerge, '_filemerge')
571 def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca, 591 def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
572 labels=None): 592 labels=None):
573 if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent(): 593 if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
574 return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca, 594 return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
575 labels=labels) 595 labels=labels)
587 (lfutil.splitstandin(orig), ahash, dhash, ohash), 607 (lfutil.splitstandin(orig), ahash, dhash, ohash),
588 0) == 1)): 608 0) == 1)):
589 repo.wwrite(fcd.path(), fco.data(), fco.flags()) 609 repo.wwrite(fcd.path(), fco.data(), fco.flags())
590 return True, 0, False 610 return True, 0, False
591 611
612 @eh.wrapfunction(copiesmod, 'pathcopies')
592 def copiespathcopies(orig, ctx1, ctx2, match=None): 613 def copiespathcopies(orig, ctx1, ctx2, match=None):
593 copies = orig(ctx1, ctx2, match=match) 614 copies = orig(ctx1, ctx2, match=match)
594 updated = {} 615 updated = {}
595 616
596 for k, v in copies.iteritems(): 617 for k, v in copies.iteritems():
601 # Copy first changes the matchers to match standins instead of 622 # Copy first changes the matchers to match standins instead of
602 # largefiles. Then it overrides util.copyfile in that function it 623 # largefiles. Then it overrides util.copyfile in that function it
603 # checks if the destination largefile already exists. It also keeps a 624 # checks if the destination largefile already exists. It also keeps a
604 # list of copied files so that the largefiles can be copied and the 625 # list of copied files so that the largefiles can be copied and the
605 # dirstate updated. 626 # dirstate updated.
627 @eh.wrapfunction(cmdutil, 'copy')
606 def overridecopy(orig, ui, repo, pats, opts, rename=False): 628 def overridecopy(orig, ui, repo, pats, opts, rename=False):
607 # doesn't remove largefile on rename 629 # doesn't remove largefile on rename
608 if len(pats) < 2: 630 if len(pats) < 2:
609 # this isn't legal, let the original function deal with it 631 # this isn't legal, let the original function deal with it
610 return orig(ui, repo, pats, opts, rename) 632 return orig(ui, repo, pats, opts, rename)
746 # 768 #
747 # Standins are only updated (to match the hash of largefiles) before 769 # Standins are only updated (to match the hash of largefiles) before
748 # commits. Update the standins then run the original revert, changing 770 # commits. Update the standins then run the original revert, changing
749 # the matcher to hit standins instead of largefiles. Based on the 771 # the matcher to hit standins instead of largefiles. Based on the
750 # resulting standins update the largefiles. 772 # resulting standins update the largefiles.
773 @eh.wrapfunction(cmdutil, 'revert')
751 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts): 774 def overriderevert(orig, ui, repo, ctx, parents, *pats, **opts):
752 # Because we put the standins in a bad state (by updating them) 775 # Because we put the standins in a bad state (by updating them)
753 # and then return them to a correct state we need to lock to 776 # and then return them to a correct state we need to lock to
754 # prevent others from changing them in their incorrect state. 777 # prevent others from changing them in their incorrect state.
755 with repo.wlock(): 778 with repo.wlock():
855 if lfrevs: 878 if lfrevs:
856 opargs = kwargs.setdefault(r'opargs', {}) 879 opargs = kwargs.setdefault(r'opargs', {})
857 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs) 880 opargs['lfrevs'] = scmutil.revrange(repo, lfrevs)
858 return orig(ui, repo, *args, **kwargs) 881 return orig(ui, repo, *args, **kwargs)
859 882
883 @eh.wrapfunction(exchange, 'pushoperation')
860 def exchangepushoperation(orig, *args, **kwargs): 884 def exchangepushoperation(orig, *args, **kwargs):
861 """Override pushoperation constructor and store lfrevs parameter""" 885 """Override pushoperation constructor and store lfrevs parameter"""
862 lfrevs = kwargs.pop(r'lfrevs', None) 886 lfrevs = kwargs.pop(r'lfrevs', None)
863 pushop = orig(*args, **kwargs) 887 pushop = orig(*args, **kwargs)
864 pushop.lfrevs = lfrevs 888 pushop.lfrevs = lfrevs
904 '--all-largefiles is incompatible with non-local destination %s') % 928 '--all-largefiles is incompatible with non-local destination %s') %
905 d) 929 d)
906 930
907 return orig(ui, source, dest, **opts) 931 return orig(ui, source, dest, **opts)
908 932
933 @eh.wrapfunction(hg, 'clone')
909 def hgclone(orig, ui, opts, *args, **kwargs): 934 def hgclone(orig, ui, opts, *args, **kwargs):
910 result = orig(ui, opts, *args, **kwargs) 935 result = orig(ui, opts, *args, **kwargs)
911 936
912 if result is not None: 937 if result is not None:
913 sourcerepo, destrepo = result 938 sourcerepo, destrepo = result
951 try: 976 try:
952 return orig(ui, repo.unfiltered(), dest, **opts) 977 return orig(ui, repo.unfiltered(), dest, **opts)
953 finally: 978 finally:
954 repo.unfiltered().lfstatus = False 979 repo.unfiltered().lfstatus = False
955 980
981 @eh.wrapfunction(webcommands, 'archive')
956 def hgwebarchive(orig, web): 982 def hgwebarchive(orig, web):
957 web.repo.lfstatus = True 983 web.repo.lfstatus = True
958 984
959 try: 985 try:
960 return orig(web) 986 return orig(web)
961 finally: 987 finally:
962 web.repo.lfstatus = False 988 web.repo.lfstatus = False
963 989
990 @eh.wrapfunction(archival, 'archive')
964 def overridearchive(orig, repo, dest, node, kind, decode=True, match=None, 991 def overridearchive(orig, repo, dest, node, kind, decode=True, match=None,
965 prefix='', mtime=None, subrepos=None): 992 prefix='', mtime=None, subrepos=None):
966 # For some reason setting repo.lfstatus in hgwebarchive only changes the 993 # For some reason setting repo.lfstatus in hgwebarchive only changes the
967 # unfiltered repo's attr, so check that as well. 994 # unfiltered repo's attr, so check that as well.
968 if not repo.lfstatus and not repo.unfiltered().lfstatus: 995 if not repo.lfstatus and not repo.unfiltered().lfstatus:
1027 sub._repo.lfstatus = True 1054 sub._repo.lfstatus = True
1028 sub.archive(archiver, prefix, submatch) 1055 sub.archive(archiver, prefix, submatch)
1029 1056
1030 archiver.done() 1057 archiver.done()
1031 1058
1059 @eh.wrapfunction(subrepo.hgsubrepo, 'archive')
1032 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True): 1060 def hgsubrepoarchive(orig, repo, archiver, prefix, match=None, decode=True):
1033 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled') 1061 lfenabled = util.safehasattr(repo._repo, '_largefilesenabled')
1034 if not lfenabled or not repo._repo.lfstatus: 1062 if not lfenabled or not repo._repo.lfstatus:
1035 return orig(repo, archiver, prefix, match, decode) 1063 return orig(repo, archiver, prefix, match, decode)
1036 1064
1081 1109
1082 # If a largefile is modified, the change is not reflected in its 1110 # If a largefile is modified, the change is not reflected in its
1083 # standin until a commit. cmdutil.bailifchanged() raises an exception 1111 # standin until a commit. cmdutil.bailifchanged() raises an exception
1084 # if the repo has uncommitted changes. Wrap it to also check if 1112 # if the repo has uncommitted changes. Wrap it to also check if
1085 # largefiles were changed. This is used by bisect, backout and fetch. 1113 # largefiles were changed. This is used by bisect, backout and fetch.
1114 @eh.wrapfunction(cmdutil, 'bailifchanged')
1086 def overridebailifchanged(orig, repo, *args, **kwargs): 1115 def overridebailifchanged(orig, repo, *args, **kwargs):
1087 orig(repo, *args, **kwargs) 1116 orig(repo, *args, **kwargs)
1088 repo.lfstatus = True 1117 repo.lfstatus = True
1089 s = repo.status() 1118 s = repo.status()
1090 repo.lfstatus = False 1119 repo.lfstatus = False
1091 if s.modified or s.added or s.removed or s.deleted: 1120 if s.modified or s.added or s.removed or s.deleted:
1092 raise error.Abort(_('uncommitted changes')) 1121 raise error.Abort(_('uncommitted changes'))
1093 1122
1123 @eh.wrapfunction(cmdutil, 'postcommitstatus')
1094 def postcommitstatus(orig, repo, *args, **kwargs): 1124 def postcommitstatus(orig, repo, *args, **kwargs):
1095 repo.lfstatus = True 1125 repo.lfstatus = True
1096 try: 1126 try:
1097 return orig(repo, *args, **kwargs) 1127 return orig(repo, *args, **kwargs)
1098 finally: 1128 finally:
1099 repo.lfstatus = False 1129 repo.lfstatus = False
1100 1130
1131 @eh.wrapfunction(cmdutil, 'forget')
1101 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly, dryrun, 1132 def cmdutilforget(orig, ui, repo, match, prefix, explicitonly, dryrun,
1102 interactive): 1133 interactive):
1103 normalmatcher = composenormalfilematcher(match, repo[None].manifest()) 1134 normalmatcher = composenormalfilematcher(match, repo[None].manifest())
1104 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly, dryrun, 1135 bad, forgot = orig(ui, repo, normalmatcher, prefix, explicitonly, dryrun,
1105 interactive) 1136 interactive)
1242 repo.lfstatus = True 1273 repo.lfstatus = True
1243 orig(ui, repo, *pats, **opts) 1274 orig(ui, repo, *pats, **opts)
1244 finally: 1275 finally:
1245 repo.lfstatus = False 1276 repo.lfstatus = False
1246 1277
1278 @eh.wrapfunction(scmutil, 'addremove')
1247 def scmutiladdremove(orig, repo, matcher, prefix, opts=None): 1279 def scmutiladdremove(orig, repo, matcher, prefix, opts=None):
1248 if opts is None: 1280 if opts is None:
1249 opts = {} 1281 opts = {}
1250 if not lfutil.islfilesrepo(repo): 1282 if not lfutil.islfilesrepo(repo):
1251 return orig(repo, matcher, prefix, opts) 1283 return orig(repo, matcher, prefix, opts)
1418 for chunk in util.filechunkiter(fpin): 1450 for chunk in util.filechunkiter(fpin):
1419 fp.write(chunk) 1451 fp.write(chunk)
1420 err = 0 1452 err = 0
1421 return err 1453 return err
1422 1454
1455 @eh.wrapfunction(merge, 'update')
1423 def mergeupdate(orig, repo, node, branchmerge, force, 1456 def mergeupdate(orig, repo, node, branchmerge, force,
1424 *args, **kwargs): 1457 *args, **kwargs):
1425 matcher = kwargs.get(r'matcher', None) 1458 matcher = kwargs.get(r'matcher', None)
1426 # note if this is a partial update 1459 # note if this is a partial update
1427 partial = matcher and not matcher.always() 1460 partial = matcher and not matcher.always()
1495 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist, 1528 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1496 normallookup=partial) 1529 normallookup=partial)
1497 1530
1498 return result 1531 return result
1499 1532
1533 @eh.wrapfunction(scmutil, 'marktouched')
1500 def scmutilmarktouched(orig, repo, files, *args, **kwargs): 1534 def scmutilmarktouched(orig, repo, files, *args, **kwargs):
1501 result = orig(repo, files, *args, **kwargs) 1535 result = orig(repo, files, *args, **kwargs)
1502 1536
1503 filelist = [] 1537 filelist = []
1504 for f in files: 1538 for f in files:
1509 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist, 1543 lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
1510 printmessage=False, normallookup=True) 1544 printmessage=False, normallookup=True)
1511 1545
1512 return result 1546 return result
1513 1547
1548 @eh.wrapfunction(upgrade, 'preservedrequirements')
1549 @eh.wrapfunction(upgrade, 'supporteddestrequirements')
1514 def upgraderequirements(orig, repo): 1550 def upgraderequirements(orig, repo):
1515 reqs = orig(repo) 1551 reqs = orig(repo)
1516 if 'largefiles' in repo.requirements: 1552 if 'largefiles' in repo.requirements:
1517 reqs.add('largefiles') 1553 reqs.add('largefiles')
1518 return reqs 1554 return reqs
1519 1555
1520 _lfscheme = 'largefile://' 1556 _lfscheme = 'largefile://'
1557
1558 @eh.wrapfunction(urlmod, 'open')
1521 def openlargefile(orig, ui, url_, data=None): 1559 def openlargefile(orig, ui, url_, data=None):
1522 if url_.startswith(_lfscheme): 1560 if url_.startswith(_lfscheme):
1523 if data: 1561 if data:
1524 msg = "cannot use data on a 'largefile://' url" 1562 msg = "cannot use data on a 'largefile://' url"
1525 raise error.ProgrammingError(msg) 1563 raise error.ProgrammingError(msg)