reposetup.py
376 lines
| 16.8 KiB
| text/x-python
|
PythonLexer
|
r15168 | # Copyright 2009-2010 Gregory P. Ward | ||
# Copyright 2009-2010 Intelerad Medical Systems Incorporated | ||||
# Copyright 2010-2011 Fog Creek Software | ||||
# Copyright 2010-2011 Unity Technologies | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
# GNU General Public License version 2 or any later version. | ||||
'''setup for largefiles repositories: reposetup''' | ||||
import copy | ||||
|
r26587 | from mercurial import error, match as match_, error | ||
|
r15168 | from mercurial.i18n import _ | ||
|
r23958 | from mercurial import scmutil, localrepo | ||
|
r15168 | |||
import lfcommands | ||||
import lfutil | ||||
def reposetup(ui, repo): | ||||
|
r20858 | # wire repositories should be given new wireproto functions | ||
# by "proto.wirereposetup()" via "hg.wirepeersetupfuncs" | ||||
|
r15168 | if not repo.local(): | ||
|
r20858 | return | ||
|
r15168 | |||
|
r16247 | class lfilesrepo(repo.__class__): | ||
|
r24158 | # the mark to examine whether "repo" object enables largefiles or not | ||
_largefilesenabled = True | ||||
|
r15168 | lfstatus = False | ||
def status_nolfiles(self, *args, **kwargs): | ||||
|
r16247 | return super(lfilesrepo, self).status(*args, **kwargs) | ||
|
r15168 | |||
|
r15252 | # When lfstatus is set, return a context that gives the names | ||
# of largefiles instead of their corresponding standins and | ||||
# identifies the largefiles as always binary, regardless of | ||||
# their actual contents. | ||||
|
r15168 | def __getitem__(self, changeid): | ||
|
r16247 | ctx = super(lfilesrepo, self).__getitem__(changeid) | ||
|
r23958 | if self.lfstatus: | ||
|
r16247 | class lfilesctx(ctx.__class__): | ||
|
r15168 | def files(self): | ||
|
r16247 | filenames = super(lfilesctx, self).files() | ||
|
r15628 | return [lfutil.splitstandin(f) or f for f in filenames] | ||
|
r15168 | def manifest(self): | ||
|
r16247 | man1 = super(lfilesctx, self).manifest() | ||
|
r24428 | class lfilesmanifest(man1.__class__): | ||
|
r24287 | def __contains__(self, filename): | ||
orig = super(lfilesmanifest, self).__contains__ | ||||
return (orig(filename) or | ||||
orig(lfutil.standin(filename))) | ||||
man1.__class__ = lfilesmanifest | ||||
|
r15168 | return man1 | ||
def filectx(self, path, fileid=None, filelog=None): | ||||
|
r22517 | orig = super(lfilesctx, self).filectx | ||
|
r15168 | try: | ||
|
r16141 | if filelog is not None: | ||
|
r22517 | result = orig(path, fileid, filelog) | ||
|
r16141 | else: | ||
|
r22517 | result = orig(path, fileid) | ||
|
r15168 | except error.LookupError: | ||
# Adding a null character will cause Mercurial to | ||||
# identify this as a binary file. | ||||
|
r16141 | if filelog is not None: | ||
|
r22517 | result = orig(lfutil.standin(path), fileid, | ||
filelog) | ||||
|
r16141 | else: | ||
|
r22517 | result = orig(lfutil.standin(path), fileid) | ||
|
r15168 | olddata = result.data | ||
result.data = lambda: olddata() + '\0' | ||||
return result | ||||
|
r16247 | ctx.__class__ = lfilesctx | ||
|
r15168 | return ctx | ||
# Figure out the status of big files and insert them into the | ||||
|
r15252 | # appropriate list in the result. Also removes standin files | ||
# from the listing. Revert to the original status if | ||||
# self.lfstatus is False. | ||||
|
r23958 | # XXX large file status is buggy when used on repo proxy. | ||
# XXX this needs to be investigated. | ||||
@localrepo.unfilteredmethod | ||||
|
r15168 | def status(self, node1='.', node2=None, match=None, ignored=False, | ||
clean=False, unknown=False, listsubrepos=False): | ||||
listignored, listclean, listunknown = ignored, clean, unknown | ||||
|
r22518 | orig = super(lfilesrepo, self).status | ||
|
r23958 | if not self.lfstatus: | ||
|
r22518 | return orig(node1, node2, match, listignored, listclean, | ||
listunknown, listsubrepos) | ||||
|
r22515 | |||
# some calls in this function rely on the old version of status | ||||
|
r23958 | self.lfstatus = False | ||
|
r22515 | ctx1 = self[node1] | ||
ctx2 = self[node2] | ||||
working = ctx2.rev() is None | ||||
parentworking = working and ctx1 == self['.'] | ||||
|
r15168 | |||
|
r22515 | if match is None: | ||
match = match_.always(self.root, self.getcwd()) | ||||
|
r15168 | |||
|
r22515 | wlock = None | ||
try: | ||||
|
r19056 | try: | ||
|
r22515 | # updating the dirstate is optional | ||
# so we don't wait on the lock | ||||
wlock = self.wlock(False) | ||||
except error.LockError: | ||||
pass | ||||
|
r15653 | |||
|
r23146 | # First check if paths or patterns were specified on the | ||
# command line. If there were, and they don't match any | ||||
|
r22515 | # largefiles, we should just bail here and let super | ||
# handle it -- thus gaining a big performance boost. | ||||
lfdirstate = lfutil.openlfdirstate(ui, self) | ||||
|
r23146 | if not match.always(): | ||
|
r22515 | for f in lfdirstate: | ||
if match(f): | ||||
break | ||||
else: | ||||
|
r22518 | return orig(node1, node2, match, listignored, listclean, | ||
listunknown, listsubrepos) | ||||
|
r15168 | |||
|
r22515 | # Create a copy of match that matches standins instead | ||
# of largefiles. | ||||
def tostandins(files): | ||||
if not working: | ||||
return files | ||||
newfiles = [] | ||||
dirstate = self.dirstate | ||||
for f in files: | ||||
sf = lfutil.standin(f) | ||||
if sf in dirstate: | ||||
newfiles.append(sf) | ||||
elif sf in dirstate.dirs(): | ||||
# Directory entries could be regular or | ||||
# standin, check both | ||||
newfiles.extend((f, sf)) | ||||
else: | ||||
newfiles.append(f) | ||||
return newfiles | ||||
|
r18149 | |||
|
r22515 | m = copy.copy(match) | ||
m._files = tostandins(m._files) | ||||
|
r22518 | result = orig(node1, node2, m, ignored, clean, unknown, | ||
listsubrepos) | ||||
|
r22515 | if working: | ||
|
r15617 | |||
|
r22515 | def sfindirstate(f): | ||
sf = lfutil.standin(f) | ||||
dirstate = self.dirstate | ||||
return sf in dirstate or sf in dirstate.dirs() | ||||
|
r18149 | |||
|
r22515 | match._files = [f for f in match._files | ||
if sfindirstate(f)] | ||||
# Don't waste time getting the ignored and unknown | ||||
# files from lfdirstate | ||||
|
r22911 | unsure, s = lfdirstate.status(match, [], False, listclean, | ||
False) | ||||
|
r22919 | (modified, added, removed, clean) = (s.modified, s.added, | ||
s.removed, s.clean) | ||||
|
r22515 | if parentworking: | ||
for lfile in unsure: | ||||
standin = lfutil.standin(lfile) | ||||
if standin not in ctx1: | ||||
# from second parent | ||||
modified.append(lfile) | ||||
elif ctx1[standin].data().strip() \ | ||||
!= lfutil.hashfile(self.wjoin(lfile)): | ||||
modified.append(lfile) | ||||
else: | ||||
|
r22523 | if listclean: | ||
clean.append(lfile) | ||||
|
r22515 | lfdirstate.normal(lfile) | ||
else: | ||||
tocheck = unsure + modified + added + clean | ||||
modified, added, clean = [], [], [] | ||||
|
r23383 | checkexec = self.dirstate._checkexec | ||
|
r22515 | |||
for lfile in tocheck: | ||||
standin = lfutil.standin(lfile) | ||||
|
r23043 | if standin in ctx1: | ||
|
r23090 | abslfile = self.wjoin(lfile) | ||
if ((ctx1[standin].data().strip() != | ||||
lfutil.hashfile(abslfile)) or | ||||
|
r23383 | (checkexec and | ||
('x' in ctx1.flags(standin)) != | ||||
|
r23090 | bool(lfutil.getexecutable(abslfile)))): | ||
|
r15168 | modified.append(lfile) | ||
|
r22523 | elif listclean: | ||
|
r15168 | clean.append(lfile) | ||
|
r22515 | else: | ||
added.append(lfile) | ||||
|
r15168 | |||
|
r23089 | # at this point, 'removed' contains largefiles | ||
# marked as 'R' in the working context. | ||||
# then, largefiles not managed also in the target | ||||
# context should be excluded from 'removed'. | ||||
removed = [lfile for lfile in removed | ||||
if lfutil.standin(lfile) in ctx1] | ||||
|
r22515 | # Standins no longer found in lfdirstate has been | ||
# removed | ||||
|
r22525 | for standin in ctx1.walk(lfutil.getstandinmatcher(self)): | ||
|
r22515 | lfile = lfutil.splitstandin(standin) | ||
if not match(lfile): | ||||
continue | ||||
if lfile not in lfdirstate: | ||||
removed.append(lfile) | ||||
|
r15663 | |||
|
r22515 | # Filter result lists | ||
result = list(result) | ||||
|
r19056 | |||
|
r22515 | # Largefiles are not really removed when they're | ||
# still in the normal dirstate. Likewise, normal | ||||
# files are not really removed if they are still in | ||||
# lfdirstate. This happens in merges where files | ||||
# change type. | ||||
removed = [f for f in removed | ||||
if f not in self.dirstate] | ||||
result[2] = [f for f in result[2] | ||||
if f not in lfdirstate] | ||||
|
r15663 | |||
|
r22515 | lfiles = set(lfdirstate._map) | ||
# Unknown files | ||||
result[4] = set(result[4]).difference(lfiles) | ||||
# Ignored files | ||||
result[5] = set(result[5]).difference(lfiles) | ||||
# combine normal files and largefiles | ||||
normals = [[fn for fn in filelist | ||||
if not lfutil.isstandin(fn)] | ||||
for filelist in result] | ||||
|
r22919 | lfstatus = (modified, added, removed, s.deleted, [], [], | ||
|
r22524 | clean) | ||
|
r22515 | result = [sorted(list1 + list2) | ||
|
r22524 | for (list1, list2) in zip(normals, lfstatus)] | ||
|
r23044 | else: # not against working directory | ||
result = [[lfutil.splitstandin(f) or f for f in items] | ||||
|
r22515 | for items in result] | ||
|
r15663 | |||
|
r22515 | if wlock: | ||
lfdirstate.write() | ||||
finally: | ||||
if wlock: | ||||
wlock.release() | ||||
|
r15168 | |||
|
r23958 | self.lfstatus = True | ||
|
r22914 | return scmutil.status(*result) | ||
|
r15168 | |||
|
r23184 | def commitctx(self, ctx, *args, **kwargs): | ||
node = super(lfilesrepo, self).commitctx(ctx, *args, **kwargs) | ||||
class lfilesctx(ctx.__class__): | ||||
def markcommitted(self, node): | ||||
orig = super(lfilesctx, self).markcommitted | ||||
return lfutil.markcommitted(orig, self, node) | ||||
ctx.__class__ = lfilesctx | ||||
|
r15168 | return node | ||
|
r15254 | # Before commit, largefile standins have not had their | ||
# contents updated to reflect the hash of their largefile. | ||||
# Do that here. | ||||
|
r15168 | def commit(self, text="", user=None, date=None, match=None, | ||
force=False, editor=False, extra={}): | ||||
|
r16247 | orig = super(lfilesrepo, self).commit | ||
|
r15168 | |||
|
r27842 | with self.wlock(): | ||
|
r23186 | lfcommithook = self._lfcommithooks[-1] | ||
match = lfcommithook(self, match) | ||||
|
r15794 | result = orig(text=text, user=user, date=date, match=match, | ||
|
r15168 | force=force, editor=editor, extra=extra) | ||
|
r15794 | return result | ||
|
r15168 | |||
def push(self, remote, force=False, revs=None, newbranch=False): | ||||
|
r19779 | if remote.local(): | ||
missing = set(self.requirements) - remote.local().supported | ||||
if missing: | ||||
msg = _("required features are not" | ||||
" supported in the destination:" | ||||
" %s") % (', '.join(sorted(missing))) | ||||
|
r26587 | raise error.Abort(msg) | ||
|
r20177 | return super(lfilesrepo, self).push(remote, force=force, revs=revs, | ||
newbranch=newbranch) | ||||
|
r15168 | |||
|
r23185 | # TODO: _subdirlfs should be moved into "lfutil.py", because | ||
# it is referred only from "lfutil.updatestandinsbymatch" | ||||
|
r18064 | def _subdirlfs(self, files, lfiles): | ||
''' | ||||
Adjust matched file list | ||||
|
r23543 | If we pass a directory to commit whose only committable files | ||
|
r18064 | are largefiles, the core commit code aborts before finding | ||
the largefiles. | ||||
So we do the following: | ||||
For directories that only have largefiles as matches, | ||||
|
r18644 | we explicitly add the largefiles to the match list and remove | ||
|
r18064 | the directory. | ||
In other cases, we leave the match list unmodified. | ||||
''' | ||||
actualfiles = [] | ||||
dirs = [] | ||||
regulars = [] | ||||
for f in files: | ||||
if lfutil.isstandin(f + '/'): | ||||
|
r26587 | raise error.Abort( | ||
|
r18064 | _('file "%s" is a largefile standin') % f, | ||
hint=('commit the largefile itself instead')) | ||||
# Scan directories | ||||
|
r28716 | if self.wvfs.isdir(f): | ||
|
r18064 | dirs.append(f) | ||
else: | ||||
regulars.append(f) | ||||
for f in dirs: | ||||
matcheddir = False | ||||
d = self.dirstate.normalize(f) + '/' | ||||
# Check for matched normal files | ||||
for mf in regulars: | ||||
if self.dirstate.normalize(mf).startswith(d): | ||||
actualfiles.append(f) | ||||
matcheddir = True | ||||
break | ||||
if not matcheddir: | ||||
# If no normal match, manually append | ||||
# any matching largefiles | ||||
for lf in lfiles: | ||||
if self.dirstate.normalize(lf).startswith(d): | ||||
actualfiles.append(lf) | ||||
if not matcheddir: | ||||
|
r23923 | # There may still be normal files in the dir, so | ||
|
r24180 | # add a directory to the list, which | ||
|
r24007 | # forces status/dirstate to walk all files and | ||
# call the match function on the matcher, even | ||||
|
r24180 | # on case sensitive filesystems. | ||
|
r23923 | actualfiles.append('.') | ||
|
r18064 | matcheddir = True | ||
# Nothing in dir, so readd it | ||||
# and let commit reject it | ||||
if not matcheddir: | ||||
actualfiles.append(f) | ||||
# Always add normal files | ||||
actualfiles += regulars | ||||
return actualfiles | ||||
|
r16247 | repo.__class__ = lfilesrepo | ||
|
r15168 | |||
|
r23186 | # stack of hooks being executed before committing. | ||
# only last element ("_lfcommithooks[-1]") is used for each committing. | ||||
repo._lfcommithooks = [lfutil.updatestandinsbymatch] | ||||
|
r23188 | # Stack of status writer functions taking "*msg, **opts" arguments | ||
|
r23543 | # like "ui.status()". Only last element ("_lfstatuswriters[-1]") | ||
|
r23188 | # is used to write status out. | ||
repo._lfstatuswriters = [ui.status] | ||||
|
r28876 | def prepushoutgoinghook(pushop): | ||
|
r28878 | """Push largefiles for pushop before pushing revisions.""" | ||
lfrevs = pushop.lfrevs | ||||
if lfrevs is None: | ||||
lfrevs = pushop.outgoing.missing | ||||
if lfrevs: | ||||
|
r21044 | toupload = set() | ||
addfunc = lambda fn, lfhash: toupload.add(lfhash) | ||||
|
r28878 | lfutil.getlfilestoupload(pushop.repo, lfrevs, | ||
|
r28876 | addfunc) | ||
lfcommands.uploadlfiles(ui, pushop.repo, pushop.remote, toupload) | ||||
|
r21044 | repo.prepushoutgoinghooks.add("largefiles", prepushoutgoinghook) | ||
|
r15168 | def checkrequireslfiles(ui, repo, **kwargs): | ||
|
r25149 | if 'largefiles' not in repo.requirements and any( | ||
|
r15168 | lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()): | ||
|
r15312 | repo.requirements.add('largefiles') | ||
|
r15168 | repo._writerequirements() | ||
|
r20790 | ui.setconfig('hooks', 'changegroup.lfiles', checkrequireslfiles, | ||
'largefiles') | ||||
ui.setconfig('hooks', 'commit.lfiles', checkrequireslfiles, 'largefiles') | ||||