reposetup.py
411 lines
| 18.9 KiB
| text/x-python
|
PythonLexer
various
|
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 | ||||
import types | ||||
import os | ||||
import re | ||||
from mercurial import context, error, manifest, match as match_, \ | ||||
node, util | ||||
from mercurial.i18n import _ | ||||
import lfcommands | ||||
import proto | ||||
import lfutil | ||||
def reposetup(ui, repo): | ||||
# wire repositories should be given new wireproto functions but not the | ||||
# other largefiles modifications | ||||
if not repo.local(): | ||||
return proto.wirereposetup(ui, repo) | ||||
for name in ('status', 'commitctx', 'commit', 'push'): | ||||
method = getattr(repo, name) | ||||
#if not (isinstance(method, types.MethodType) and | ||||
# method.im_func is repo.__class__.commitctx.im_func): | ||||
if isinstance(method, types.FunctionType) and method.func_name == \ | ||||
'wrap': | ||||
ui.warn(_('largefiles: repo method %r appears to have already been' | ||||
' wrapped by another extension: ' | ||||
'largefiles may behave incorrectly\n') | ||||
% name) | ||||
class lfiles_repo(repo.__class__): | ||||
lfstatus = False | ||||
def status_nolfiles(self, *args, **kwargs): | ||||
return super(lfiles_repo, self).status(*args, **kwargs) | ||||
# When lfstatus is set, return a context that gives the names of lfiles | ||||
# instead of their corresponding standins and identifies the lfiles as | ||||
# always binary, regardless of their actual contents. | ||||
def __getitem__(self, changeid): | ||||
ctx = super(lfiles_repo, self).__getitem__(changeid) | ||||
if self.lfstatus: | ||||
class lfiles_manifestdict(manifest.manifestdict): | ||||
def __contains__(self, filename): | ||||
if super(lfiles_manifestdict, | ||||
self).__contains__(filename): | ||||
return True | ||||
return super(lfiles_manifestdict, | ||||
self).__contains__(lfutil.shortname+'/' + filename) | ||||
class lfiles_ctx(ctx.__class__): | ||||
def files(self): | ||||
filenames = super(lfiles_ctx, self).files() | ||||
Matt Mackall
|
r15170 | return [re.sub('^\\'+lfutil.shortname+'/', '', | ||
filename) for filename in filenames] | ||||
various
|
r15168 | def manifest(self): | ||
man1 = super(lfiles_ctx, self).manifest() | ||||
man1.__class__ = lfiles_manifestdict | ||||
return man1 | ||||
def filectx(self, path, fileid=None, filelog=None): | ||||
try: | ||||
result = super(lfiles_ctx, self).filectx(path, | ||||
fileid, filelog) | ||||
except error.LookupError: | ||||
# Adding a null character will cause Mercurial to | ||||
# identify this as a binary file. | ||||
result = super(lfiles_ctx, self).filectx( | ||||
lfutil.shortname + '/' + path, fileid, | ||||
filelog) | ||||
olddata = result.data | ||||
result.data = lambda: olddata() + '\0' | ||||
return result | ||||
ctx.__class__ = lfiles_ctx | ||||
return ctx | ||||
# Figure out the status of big files and insert them into the | ||||
# appropriate list in the result. Also removes standin files from | ||||
# the listing. This function reverts to the original status if | ||||
# self.lfstatus is False | ||||
def status(self, node1='.', node2=None, match=None, ignored=False, | ||||
clean=False, unknown=False, listsubrepos=False): | ||||
listignored, listclean, listunknown = ignored, clean, unknown | ||||
if not self.lfstatus: | ||||
try: | ||||
return super(lfiles_repo, self).status(node1, node2, match, | ||||
listignored, listclean, listunknown, listsubrepos) | ||||
except TypeError: | ||||
return super(lfiles_repo, self).status(node1, node2, match, | ||||
listignored, listclean, listunknown) | ||||
else: | ||||
# some calls in this function rely on the old version of status | ||||
self.lfstatus = False | ||||
if isinstance(node1, context.changectx): | ||||
ctx1 = node1 | ||||
else: | ||||
ctx1 = repo[node1] | ||||
if isinstance(node2, context.changectx): | ||||
ctx2 = node2 | ||||
else: | ||||
ctx2 = repo[node2] | ||||
working = ctx2.rev() is None | ||||
parentworking = working and ctx1 == self['.'] | ||||
def inctx(file, ctx): | ||||
try: | ||||
if ctx.rev() is None: | ||||
return file in ctx.manifest() | ||||
ctx[file] | ||||
return True | ||||
Matt Mackall
|
r15171 | except KeyError: | ||
various
|
r15168 | return False | ||
# create a copy of match that matches standins instead of | ||||
# lfiles if matcher not set then it is the always matcher so | ||||
# overwrite that | ||||
if match is None: | ||||
match = match_.always(self.root, self.getcwd()) | ||||
def tostandin(file): | ||||
if inctx(lfutil.standin(file), ctx2): | ||||
return lfutil.standin(file) | ||||
return file | ||||
m = copy.copy(match) | ||||
m._files = [tostandin(f) for f in m._files] | ||||
# get ignored clean and unknown but remove them later if they | ||||
# were not asked for | ||||
try: | ||||
result = super(lfiles_repo, self).status(node1, node2, m, | ||||
True, True, True, listsubrepos) | ||||
except TypeError: | ||||
result = super(lfiles_repo, self).status(node1, node2, m, | ||||
True, True, True) | ||||
if working: | ||||
# Hold the wlock while we read lfiles and update the | ||||
# lfdirstate | ||||
wlock = repo.wlock() | ||||
try: | ||||
# Any non lfiles that were explicitly listed must be | ||||
# taken out or lfdirstate.status will report an error. | ||||
# The status of these files was already computed using | ||||
# super's status. | ||||
lfdirstate = lfutil.openlfdirstate(ui, self) | ||||
match._files = [f for f in match._files if f in | ||||
lfdirstate] | ||||
s = lfdirstate.status(match, [], listignored, | ||||
listclean, listunknown) | ||||
(unsure, modified, added, removed, missing, unknown, | ||||
ignored, clean) = s | ||||
if parentworking: | ||||
for lfile in unsure: | ||||
if ctx1[lfutil.standin(lfile)].data().strip() \ | ||||
!= lfutil.hashfile(self.wjoin(lfile)): | ||||
modified.append(lfile) | ||||
else: | ||||
clean.append(lfile) | ||||
lfdirstate.normal(lfile) | ||||
lfdirstate.write() | ||||
else: | ||||
tocheck = unsure + modified + added + clean | ||||
modified, added, clean = [], [], [] | ||||
for lfile in tocheck: | ||||
standin = lfutil.standin(lfile) | ||||
if inctx(standin, ctx1): | ||||
if ctx1[standin].data().strip() != \ | ||||
lfutil.hashfile(self.wjoin(lfile)): | ||||
modified.append(lfile) | ||||
else: | ||||
clean.append(lfile) | ||||
else: | ||||
added.append(lfile) | ||||
finally: | ||||
wlock.release() | ||||
for standin in ctx1.manifest(): | ||||
if not lfutil.isstandin(standin): | ||||
continue | ||||
lfile = lfutil.splitstandin(standin) | ||||
if not match(lfile): | ||||
continue | ||||
if lfile not in lfdirstate: | ||||
removed.append(lfile) | ||||
# Handle unknown and ignored differently | ||||
lfiles = (modified, added, removed, missing, [], [], clean) | ||||
result = list(result) | ||||
# Unknown files | ||||
result[4] = [f for f in unknown if repo.dirstate[f] == '?'\ | ||||
and not lfutil.isstandin(f)] | ||||
# Ignored files must be ignored by both the dirstate and | ||||
# lfdirstate | ||||
result[5] = set(ignored).intersection(set(result[5])) | ||||
# combine normal files and lfiles | ||||
normals = [[fn for fn in filelist if not \ | ||||
lfutil.isstandin(fn)] for filelist in result] | ||||
result = [sorted(list1 + list2) for (list1, list2) in \ | ||||
zip(normals, lfiles)] | ||||
else: | ||||
def toname(f): | ||||
if lfutil.isstandin(f): | ||||
return lfutil.splitstandin(f) | ||||
return f | ||||
result = [[toname(f) for f in items] for items in result] | ||||
if not listunknown: | ||||
result[4] = [] | ||||
if not listignored: | ||||
result[5] = [] | ||||
if not listclean: | ||||
result[6] = [] | ||||
self.lfstatus = True | ||||
return result | ||||
# This call happens after a commit has occurred. Copy all of the lfiles | ||||
# into the cache | ||||
def commitctx(self, *args, **kwargs): | ||||
node = super(lfiles_repo, self).commitctx(*args, **kwargs) | ||||
ctx = self[node] | ||||
for filename in ctx.files(): | ||||
if lfutil.isstandin(filename) and filename in ctx.manifest(): | ||||
realfile = lfutil.splitstandin(filename) | ||||
lfutil.copytocache(self, ctx.node(), realfile) | ||||
return node | ||||
# This call happens before a commit has occurred. The lfile standins | ||||
# have not had their contents updated (to reflect the hash of their | ||||
# lfile). Do that here. | ||||
def commit(self, text="", user=None, date=None, match=None, | ||||
force=False, editor=False, extra={}): | ||||
orig = super(lfiles_repo, self).commit | ||||
wlock = repo.wlock() | ||||
try: | ||||
if getattr(repo, "_isrebasing", False): | ||||
# We have to take the time to pull down the new lfiles now. | ||||
# Otherwise if we are rebasing, any lfiles that were | ||||
# modified in the changesets we are rebasing on top of get | ||||
# overwritten either by the rebase or in the first commit | ||||
# after the rebase. | ||||
lfcommands.updatelfiles(repo.ui, repo) | ||||
# Case 1: user calls commit with no specific files or | ||||
# include/exclude patterns: refresh and commit everything. | ||||
if (match is None) or (not match.anypats() and not \ | ||||
match.files()): | ||||
lfiles = lfutil.listlfiles(self) | ||||
lfdirstate = lfutil.openlfdirstate(ui, self) | ||||
# this only loops through lfiles that exist (not | ||||
# removed/renamed) | ||||
for lfile in lfiles: | ||||
if os.path.exists(self.wjoin(lfutil.standin(lfile))): | ||||
# this handles the case where a rebase is being | ||||
# performed and the working copy is not updated | ||||
# yet. | ||||
if os.path.exists(self.wjoin(lfile)): | ||||
lfutil.updatestandin(self, | ||||
lfutil.standin(lfile)) | ||||
lfdirstate.normal(lfile) | ||||
for lfile in lfdirstate: | ||||
if not os.path.exists( | ||||
repo.wjoin(lfutil.standin(lfile))): | ||||
try: | ||||
# Mercurial >= 1.9 | ||||
lfdirstate.drop(lfile) | ||||
except AttributeError: | ||||
# Mercurial <= 1.8 | ||||
lfdirstate.forget(lfile) | ||||
lfdirstate.write() | ||||
return orig(text=text, user=user, date=date, match=match, | ||||
force=force, editor=editor, extra=extra) | ||||
for file in match.files(): | ||||
if lfutil.isstandin(file): | ||||
raise util.Abort( | ||||
"Don't commit largefile standin. Commit largefile.") | ||||
# Case 2: user calls commit with specified patterns: refresh | ||||
# any matching big files. | ||||
smatcher = lfutil.composestandinmatcher(self, match) | ||||
standins = lfutil.dirstate_walk(self.dirstate, smatcher) | ||||
# No matching big files: get out of the way and pass control to | ||||
# the usual commit() method. | ||||
if not standins: | ||||
return orig(text=text, user=user, date=date, match=match, | ||||
force=force, editor=editor, extra=extra) | ||||
# Refresh all matching big files. It's possible that the | ||||
# commit will end up failing, in which case the big files will | ||||
# stay refreshed. No harm done: the user modified them and | ||||
# asked to commit them, so sooner or later we're going to | ||||
# refresh the standins. Might as well leave them refreshed. | ||||
lfdirstate = lfutil.openlfdirstate(ui, self) | ||||
for standin in standins: | ||||
lfile = lfutil.splitstandin(standin) | ||||
if lfdirstate[lfile] <> 'r': | ||||
lfutil.updatestandin(self, standin) | ||||
lfdirstate.normal(lfile) | ||||
else: | ||||
try: | ||||
# Mercurial >= 1.9 | ||||
lfdirstate.drop(lfile) | ||||
except AttributeError: | ||||
# Mercurial <= 1.8 | ||||
lfdirstate.forget(lfile) | ||||
lfdirstate.write() | ||||
# Cook up a new matcher that only matches regular files or | ||||
# standins corresponding to the big files requested by the | ||||
# user. Have to modify _files to prevent commit() from | ||||
# complaining "not tracked" for big files. | ||||
lfiles = lfutil.listlfiles(repo) | ||||
match = copy.copy(match) | ||||
orig_matchfn = match.matchfn | ||||
# Check both the list of lfiles and the list of standins | ||||
# because if a lfile was removed, it won't be in the list of | ||||
# lfiles at this point | ||||
match._files += sorted(standins) | ||||
actualfiles = [] | ||||
for f in match._files: | ||||
fstandin = lfutil.standin(f) | ||||
# Ignore known lfiles and standins | ||||
if f in lfiles or fstandin in standins: | ||||
continue | ||||
# Append directory separator to avoid collisions | ||||
if not fstandin.endswith(os.sep): | ||||
fstandin += os.sep | ||||
# Prevalidate matching standin directories | ||||
if lfutil.any_(st for st in match._files if \ | ||||
st.startswith(fstandin)): | ||||
continue | ||||
actualfiles.append(f) | ||||
match._files = actualfiles | ||||
def matchfn(f): | ||||
if orig_matchfn(f): | ||||
return f not in lfiles | ||||
else: | ||||
return f in standins | ||||
match.matchfn = matchfn | ||||
return orig(text=text, user=user, date=date, match=match, | ||||
force=force, editor=editor, extra=extra) | ||||
finally: | ||||
wlock.release() | ||||
def push(self, remote, force=False, revs=None, newbranch=False): | ||||
o = lfutil.findoutgoing(repo, remote, force) | ||||
if o: | ||||
toupload = set() | ||||
o = repo.changelog.nodesbetween(o, revs)[0] | ||||
for n in o: | ||||
parents = [p for p in repo.changelog.parents(n) if p != \ | ||||
node.nullid] | ||||
ctx = repo[n] | ||||
files = set(ctx.files()) | ||||
if len(parents) == 2: | ||||
mc = ctx.manifest() | ||||
mp1 = ctx.parents()[0].manifest() | ||||
mp2 = ctx.parents()[1].manifest() | ||||
for f in mp1: | ||||
if f not in mc: | ||||
files.add(f) | ||||
for f in mp2: | ||||
if f not in mc: | ||||
files.add(f) | ||||
for f in mc: | ||||
if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, | ||||
None): | ||||
files.add(f) | ||||
toupload = toupload.union(set([ctx[f].data().strip() for f\ | ||||
in files if lfutil.isstandin(f) and f in ctx])) | ||||
lfcommands.uploadlfiles(ui, self, remote, toupload) | ||||
# Mercurial >= 1.6 takes the newbranch argument, try that first. | ||||
try: | ||||
return super(lfiles_repo, self).push(remote, force, revs, | ||||
newbranch) | ||||
except TypeError: | ||||
return super(lfiles_repo, self).push(remote, force, revs) | ||||
repo.__class__ = lfiles_repo | ||||
def checkrequireslfiles(ui, repo, **kwargs): | ||||
if 'largefiles' not in repo.requirements and lfutil.any_( | ||||
lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()): | ||||
# work around bug in mercurial 1.9 whereby requirements is a list | ||||
# on newly-cloned repos | ||||
repo.requirements = set(repo.requirements) | ||||
repo.requirements |= set(['largefiles']) | ||||
repo._writerequirements() | ||||
checkrequireslfiles(ui, repo) | ||||
ui.setconfig('hooks', 'changegroup.lfiles', checkrequireslfiles) | ||||
ui.setconfig('hooks', 'commit.lfiles', checkrequireslfiles) | ||||