mirror of
https://github.com/facebook/sapling.git
synced 2024-10-11 09:17:30 +03:00
5eecca9702
repo.sopener has been deprecated since hg 2.3, and repo.svfs replaces it. Since it's been dead for so long, let's just use svfs and call it good enough.
173 lines
6.5 KiB
Python
173 lines
6.5 KiB
Python
# shallowrepo.py - shallow repository that uses remote filelogs
|
|
#
|
|
# Copyright 2013 Facebook, Inc.
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2 or any later version.
|
|
|
|
from mercurial.node import hex, nullid, nullrev, bin
|
|
from mercurial.i18n import _
|
|
from mercurial import localrepo, context, util, match, scmutil
|
|
from mercurial.extensions import wrapfunction
|
|
import remotefilelog, remotefilectx, fileserverclient, shallowbundle, os
|
|
|
|
requirement = "remotefilelog"
|
|
|
|
def wraprepo(repo):
|
|
class shallowrepository(repo.__class__):
|
|
@util.propertycache
|
|
def name(self):
|
|
return self.ui.config('remotefilelog', 'reponame', '')
|
|
|
|
@util.propertycache
|
|
def fallbackpath(self):
|
|
return repo.ui.config("remotefilelog", "fallbackpath",
|
|
# fallbackrepo is the old, deprecated name
|
|
repo.ui.config("remotefilelog", "fallbackrepo",
|
|
repo.ui.config("paths", "default")))
|
|
|
|
def sparsematch(self, *revs):
|
|
baseinstance = super(shallowrepository, self)
|
|
if util.safehasattr(baseinstance, 'sparsematch'):
|
|
return baseinstance.sparsematch(*revs)
|
|
|
|
return None
|
|
|
|
def file(self, f):
|
|
if f[0] == '/':
|
|
f = f[1:]
|
|
|
|
if self.shallowmatch(f):
|
|
return remotefilelog.remotefilelog(self.svfs, f, self)
|
|
else:
|
|
return super(shallowrepository, self).file(f)
|
|
|
|
def filectx(self, path, changeid=None, fileid=None):
|
|
if self.shallowmatch(path):
|
|
return remotefilectx.remotefilectx(self, path, changeid, fileid)
|
|
else:
|
|
return super(shallowrepository, self).filectx(path, changeid, fileid)
|
|
|
|
@localrepo.unfilteredmethod
|
|
def commitctx(self, ctx, error=False):
|
|
"""Add a new revision to current repository.
|
|
Revision information is passed via the context argument.
|
|
"""
|
|
|
|
# prefetch files that will likely be compared
|
|
m1 = ctx.p1().manifest()
|
|
files = []
|
|
for f in ctx.modified() + ctx.added():
|
|
fparent1 = m1.get(f, nullid)
|
|
if fparent1 != nullid:
|
|
files.append((f, hex(fparent1)))
|
|
self.fileservice.prefetch(files)
|
|
return super(shallowrepository, self).commitctx(ctx, error=error)
|
|
|
|
def prefetch(self, revs, base=None, pats=None, opts=None):
|
|
"""Prefetches all the necessary file revisions for the given revs
|
|
"""
|
|
fallbackpath = self.fallbackpath
|
|
if fallbackpath:
|
|
# If we know a rev is on the server, we should fetch the server
|
|
# version of those files, since our local file versions might
|
|
# become obsolete if the local commits are stripped.
|
|
localrevs = repo.revs('outgoing(%s)', fallbackpath)
|
|
if base is not None and base != nullrev:
|
|
serverbase = list(repo.revs('first(reverse(::%s) - %ld)', base,
|
|
localrevs))
|
|
if serverbase:
|
|
base = serverbase[0]
|
|
else:
|
|
localrevs = repo
|
|
|
|
mf = repo.manifest
|
|
if base is not None:
|
|
mfdict = mf.read(repo[base].manifestnode())
|
|
skip = set(mfdict.iteritems())
|
|
else:
|
|
skip = set()
|
|
|
|
# Copy the skip set to start large and avoid constant resizing,
|
|
# and since it's likely to be very similar to the prefetch set.
|
|
files = skip.copy()
|
|
serverfiles = skip.copy()
|
|
visited = set()
|
|
visited.add(nullrev)
|
|
for rev in sorted(revs):
|
|
ctx = repo[rev]
|
|
if pats:
|
|
m = scmutil.match(ctx, pats, opts)
|
|
sparsematch = repo.sparsematch(rev)
|
|
|
|
mfnode = ctx.manifestnode()
|
|
mfrev = mf.rev(mfnode)
|
|
|
|
# Decompressing manifests is expensive.
|
|
# When possible, only read the deltas.
|
|
p1, p2 = mf.parentrevs(mfrev)
|
|
if p1 in visited and p2 in visited:
|
|
mfdict = mf.readfast(mfnode)
|
|
else:
|
|
mfdict = mf.read(mfnode)
|
|
|
|
diff = mfdict.iteritems()
|
|
if pats:
|
|
diff = (pf for pf in diff if m(pf[0]))
|
|
if sparsematch:
|
|
diff = (pf for pf in diff if sparsematch(pf[0]))
|
|
if rev not in localrevs:
|
|
serverfiles.update(diff)
|
|
else:
|
|
files.update(diff)
|
|
|
|
visited.add(mfrev)
|
|
|
|
files.difference_update(skip)
|
|
serverfiles.difference_update(skip)
|
|
|
|
# Fetch files known to be on the server
|
|
if serverfiles:
|
|
results = [(path, hex(fnode)) for (path, fnode) in serverfiles]
|
|
repo.fileservice.prefetch(results, force=True)
|
|
|
|
# Fetch files that may or may not be on the server
|
|
if files:
|
|
results = [(path, hex(fnode)) for (path, fnode) in files]
|
|
repo.fileservice.prefetch(results)
|
|
|
|
# Wrap dirstate.status here so we can prefetch all file nodes in
|
|
# the lookup set before localrepo.status uses them.
|
|
def status(orig, match, subrepos, ignored, clean, unknown):
|
|
lookup, status = orig(match, subrepos, ignored, clean, unknown)
|
|
|
|
if lookup:
|
|
files = []
|
|
parents = repo.parents()
|
|
for fname in lookup:
|
|
for ctx in parents:
|
|
if fname in ctx:
|
|
fnode = ctx.filenode(fname)
|
|
files.append((fname, hex(fnode)))
|
|
|
|
repo.fileservice.prefetch(files)
|
|
|
|
return lookup, status
|
|
|
|
wrapfunction(repo.dirstate, 'status', status)
|
|
|
|
repo.__class__ = shallowrepository
|
|
|
|
repo.shallowmatch = match.always(repo.root, '')
|
|
repo.fileservice = fileserverclient.fileserverclient(repo)
|
|
|
|
repo.includepattern = repo.ui.configlist("remotefilelog", "includepattern", None)
|
|
repo.excludepattern = repo.ui.configlist("remotefilelog", "excludepattern", None)
|
|
if repo.includepattern or repo.excludepattern:
|
|
repo.shallowmatch = match.match(repo.root, '', None,
|
|
repo.includepattern, repo.excludepattern)
|
|
|
|
localpath = os.path.join(repo.svfs.vfs.base, 'data')
|
|
if not os.path.exists(localpath):
|
|
os.makedirs(localpath)
|