reposetup.py
490 lines
| 22.5 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 os | ||||
FUJIWARA Katsunori
|
r21044 | from mercurial import error, manifest, match as match_, util | ||
various
|
r15168 | from mercurial.i18n import _ | ||
Pierre-Yves David
|
r18012 | from mercurial import localrepo | ||
various
|
r15168 | |||
import lfcommands | ||||
import lfutil | ||||
def reposetup(ui, repo): | ||||
FUJIWARA Katsunori
|
r20858 | # wire repositories should be given new wireproto functions | ||
# by "proto.wirereposetup()" via "hg.wirepeersetupfuncs" | ||||
various
|
r15168 | if not repo.local(): | ||
FUJIWARA Katsunori
|
r20858 | return | ||
various
|
r15168 | |||
Na'Tosha Bard
|
r16247 | class lfilesrepo(repo.__class__): | ||
various
|
r15168 | lfstatus = False | ||
def status_nolfiles(self, *args, **kwargs): | ||||
Na'Tosha Bard
|
r16247 | return super(lfilesrepo, self).status(*args, **kwargs) | ||
various
|
r15168 | |||
Greg Ward
|
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. | ||||
various
|
r15168 | def __getitem__(self, changeid): | ||
Na'Tosha Bard
|
r16247 | ctx = super(lfilesrepo, self).__getitem__(changeid) | ||
various
|
r15168 | if self.lfstatus: | ||
Na'Tosha Bard
|
r16247 | class lfilesmanifestdict(manifest.manifestdict): | ||
various
|
r15168 | def __contains__(self, filename): | ||
Na'Tosha Bard
|
r16247 | if super(lfilesmanifestdict, | ||
various
|
r15168 | self).__contains__(filename): | ||
return True | ||||
Na'Tosha Bard
|
r16247 | return super(lfilesmanifestdict, | ||
Martin Geisler
|
r15628 | self).__contains__(lfutil.standin(filename)) | ||
Na'Tosha Bard
|
r16247 | class lfilesctx(ctx.__class__): | ||
various
|
r15168 | def files(self): | ||
Na'Tosha Bard
|
r16247 | filenames = super(lfilesctx, self).files() | ||
Martin Geisler
|
r15628 | return [lfutil.splitstandin(f) or f for f in filenames] | ||
various
|
r15168 | def manifest(self): | ||
Na'Tosha Bard
|
r16247 | man1 = super(lfilesctx, self).manifest() | ||
man1.__class__ = lfilesmanifestdict | ||||
various
|
r15168 | return man1 | ||
def filectx(self, path, fileid=None, filelog=None): | ||||
try: | ||||
Dan Villiom Podlaski Christiansen
|
r16141 | if filelog is not None: | ||
Na'Tosha Bard
|
r16247 | result = super(lfilesctx, self).filectx( | ||
Dan Villiom Podlaski Christiansen
|
r16141 | path, fileid, filelog) | ||
else: | ||||
Na'Tosha Bard
|
r16247 | result = super(lfilesctx, self).filectx( | ||
Dan Villiom Podlaski Christiansen
|
r16141 | path, fileid) | ||
various
|
r15168 | except error.LookupError: | ||
# Adding a null character will cause Mercurial to | ||||
# identify this as a binary file. | ||||
Dan Villiom Podlaski Christiansen
|
r16141 | if filelog is not None: | ||
Na'Tosha Bard
|
r16247 | result = super(lfilesctx, self).filectx( | ||
Dan Villiom Podlaski Christiansen
|
r16141 | lfutil.standin(path), fileid, filelog) | ||
else: | ||||
Na'Tosha Bard
|
r16247 | result = super(lfilesctx, self).filectx( | ||
Dan Villiom Podlaski Christiansen
|
r16141 | lfutil.standin(path), fileid) | ||
various
|
r15168 | olddata = result.data | ||
result.data = lambda: olddata() + '\0' | ||||
return result | ||||
Na'Tosha Bard
|
r16247 | ctx.__class__ = lfilesctx | ||
various
|
r15168 | return ctx | ||
# Figure out the status of big files and insert them into the | ||||
Greg Ward
|
r15252 | # appropriate list in the result. Also removes standin files | ||
# from the listing. Revert to the original status if | ||||
# self.lfstatus is False. | ||||
Pierre-Yves David
|
r18012 | # XXX large file status is buggy when used on repo proxy. | ||
# XXX this needs to be investigated. | ||||
Pierre-Yves David
|
r18016 | @localrepo.unfilteredmethod | ||
various
|
r15168 | 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: | ||||
Na'Tosha Bard
|
r16247 | return super(lfilesrepo, self).status(node1, node2, match, | ||
Martin Geisler
|
r15626 | listignored, listclean, listunknown, listsubrepos) | ||
various
|
r15168 | else: | ||
# some calls in this function rely on the old version of status | ||||
self.lfstatus = False | ||||
Sean Farley
|
r19570 | ctx1 = self[node1] | ||
ctx2 = self[node2] | ||||
various
|
r15168 | 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 | ||
if match is None: | ||||
match = match_.always(self.root, self.getcwd()) | ||||
Mads Kiilerich
|
r19056 | wlock = None | ||
try: | ||||
try: | ||||
# updating the dirstate is optional | ||||
# so we don't wait on the lock | ||||
wlock = self.wlock(False) | ||||
except error.LockError: | ||||
pass | ||||
Na'Tosha Bard
|
r15653 | |||
Mads Kiilerich
|
r19056 | # First check if there were files specified on the | ||
# command line. If there were, and none of them were | ||||
# largefiles, we should just bail here and let super | ||||
# handle it -- thus gaining a big performance boost. | ||||
lfdirstate = lfutil.openlfdirstate(ui, self) | ||||
if match.files() and not match.anypats(): | ||||
for f in lfdirstate: | ||||
if match(f): | ||||
break | ||||
Patrick Mezard
|
r16586 | else: | ||
Mads Kiilerich
|
r19056 | return super(lfilesrepo, self).status(node1, node2, | ||
match, listignored, listclean, | ||||
listunknown, listsubrepos) | ||||
various
|
r15168 | |||
Mads Kiilerich
|
r19056 | # 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 | ||||
Mads Kiilerich
|
r18149 | |||
Mads Kiilerich
|
r19056 | m = copy.copy(match) | ||
m._files = tostandins(m._files) | ||||
Na'Tosha Bard
|
r15617 | |||
Mads Kiilerich
|
r19056 | result = super(lfilesrepo, self).status(node1, node2, m, | ||
ignored, clean, unknown, listsubrepos) | ||||
if working: | ||||
def sfindirstate(f): | ||||
sf = lfutil.standin(f) | ||||
dirstate = self.dirstate | ||||
return sf in dirstate or sf in dirstate.dirs() | ||||
Mads Kiilerich
|
r18149 | |||
Mads Kiilerich
|
r19056 | match._files = [f for f in match._files | ||
if sfindirstate(f)] | ||||
# Don't waste time getting the ignored and unknown | ||||
# files from lfdirstate | ||||
s = lfdirstate.status(match, [], False, | ||||
listclean, False) | ||||
(unsure, modified, added, removed, missing, _unknown, | ||||
_ignored, clean) = s | ||||
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)): | ||||
various
|
r15168 | modified.append(lfile) | ||
else: | ||||
clean.append(lfile) | ||||
Mads Kiilerich
|
r19056 | lfdirstate.normal(lfile) | ||
else: | ||||
tocheck = unsure + modified + added + clean | ||||
modified, added, clean = [], [], [] | ||||
various
|
r15168 | |||
Mads Kiilerich
|
r19056 | 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) | ||||
Martin Geisler
|
r15663 | |||
Mads Kiilerich
|
r19056 | # Standins no longer found in lfdirstate has been | ||
# removed | ||||
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) | ||||
# Filter result lists | ||||
result = list(result) | ||||
Martin Geisler
|
r15663 | |||
Mads Kiilerich
|
r19056 | # 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] | ||||
Martin Geisler
|
r15663 | |||
Mads Kiilerich
|
r19056 | 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] | ||||
lfiles = (modified, added, removed, missing, [], [], | ||||
clean) | ||||
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] | ||||
various
|
r15168 | |||
Mads Kiilerich
|
r19056 | if wlock: | ||
lfdirstate.write() | ||||
finally: | ||||
if wlock: | ||||
wlock.release() | ||||
Mads Kiilerich
|
r18139 | |||
various
|
r15168 | if not listunknown: | ||
result[4] = [] | ||||
if not listignored: | ||||
result[5] = [] | ||||
if not listclean: | ||||
result[6] = [] | ||||
self.lfstatus = True | ||||
return result | ||||
Greg Ward
|
r15254 | # As part of committing, copy all of the largefiles into the | ||
# cache. | ||||
various
|
r15168 | def commitctx(self, *args, **kwargs): | ||
Na'Tosha Bard
|
r16247 | node = super(lfilesrepo, self).commitctx(*args, **kwargs) | ||
Dan Villiom Podlaski Christiansen
|
r15796 | lfutil.copyalltostore(self, node) | ||
various
|
r15168 | return node | ||
Greg Ward
|
r15254 | # Before commit, largefile standins have not had their | ||
# contents updated to reflect the hash of their largefile. | ||||
# Do that here. | ||||
various
|
r15168 | def commit(self, text="", user=None, date=None, match=None, | ||
force=False, editor=False, extra={}): | ||||
Na'Tosha Bard
|
r16247 | orig = super(lfilesrepo, self).commit | ||
various
|
r15168 | |||
Pierre-Yves David
|
r17803 | wlock = self.wlock() | ||
various
|
r15168 | try: | ||
Na'Tosha Bard
|
r15982 | # Case 0: Rebase or Transplant | ||
Na'Tosha Bard
|
r15793 | # We have to take the time to pull down the new largefiles now. | ||
Na'Tosha Bard
|
r15982 | # Otherwise, any largefiles that were modified in the | ||
# destination changesets get overwritten, either by the rebase | ||||
# or in the first commit after the rebase or transplant. | ||||
Na'Tosha Bard
|
r15793 | # updatelfiles will update the dirstate to mark any pulled | ||
# largefiles as modified | ||||
Pierre-Yves David
|
r17803 | if getattr(self, "_isrebasing", False) or \ | ||
getattr(self, "_istransplanting", False): | ||||
lfcommands.updatelfiles(self.ui, self, filelist=None, | ||||
Na'Tosha Bard
|
r15982 | printmessage=False) | ||
Na'Tosha Bard
|
r15793 | result = orig(text=text, user=user, date=date, match=match, | ||
force=force, editor=editor, extra=extra) | ||||
return result | ||||
various
|
r15168 | # Case 1: user calls commit with no specific files or | ||
Na'Tosha Bard
|
r15250 | # include/exclude patterns: refresh and commit all files that | ||
# are "dirty". | ||||
Greg Ward
|
r15255 | if ((match is None) or | ||
(not match.anypats() and not match.files())): | ||||
Na'Tosha Bard
|
r15250 | # Spend a bit of time here to get a list of files we know | ||
# are modified so we can compare only against those. | ||||
# It can cost a lot of time (several seconds) | ||||
# otherwise to update all standins if the largefiles are | ||||
# large. | ||||
lfdirstate = lfutil.openlfdirstate(ui, self) | ||||
Pierre-Yves David
|
r17803 | dirtymatch = match_.always(self.root, self.getcwd()) | ||
Na'Tosha Bard
|
r15250 | s = lfdirstate.status(dirtymatch, [], False, False, False) | ||
Mads Kiilerich
|
r18726 | (unsure, modified, added, removed, _missing, _unknown, | ||
_ignored, _clean) = s | ||||
modifiedfiles = unsure + modified + added + removed | ||||
various
|
r15168 | lfiles = lfutil.listlfiles(self) | ||
Greg Ward
|
r15254 | # this only loops through largefiles that exist (not | ||
various
|
r15168 | # removed/renamed) | ||
for lfile in lfiles: | ||||
Na'Tosha Bard
|
r15250 | if lfile in modifiedfiles: | ||
Na'Tosha Bard
|
r16248 | if os.path.exists( | ||
self.wjoin(lfutil.standin(lfile))): | ||||
Na'Tosha Bard
|
r15250 | # 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) | ||||
Levi Bard
|
r15794 | |||
result = orig(text=text, user=user, date=date, match=match, | ||||
force=force, editor=editor, extra=extra) | ||||
Matt Harbison
|
r17230 | |||
if result is not None: | ||||
for lfile in lfdirstate: | ||||
if lfile in modifiedfiles: | ||||
Pierre-Yves David
|
r17803 | if (not os.path.exists(self.wjoin( | ||
Matt Harbison
|
r17230 | lfutil.standin(lfile)))) or \ | ||
Pierre-Yves David
|
r17803 | (not os.path.exists(self.wjoin(lfile))): | ||
Matt Harbison
|
r17230 | lfdirstate.drop(lfile) | ||
Levi Bard
|
r15794 | # This needs to be after commit; otherwise precommit hooks | ||
# get the wrong status | ||||
various
|
r15168 | lfdirstate.write() | ||
Levi Bard
|
r15794 | return result | ||
various
|
r15168 | |||
Levi Bard
|
r18064 | lfiles = lfutil.listlfiles(self) | ||
match._files = self._subdirlfs(match.files(), lfiles) | ||||
various
|
r15168 | |||
# Case 2: user calls commit with specified patterns: refresh | ||||
# any matching big files. | ||||
smatcher = lfutil.composestandinmatcher(self, match) | ||||
Mads Kiilerich
|
r18154 | standins = self.dirstate.walk(smatcher, [], False, False) | ||
various
|
r15168 | |||
# 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) | ||||
Augie Fackler
|
r18182 | if lfdirstate[lfile] != 'r': | ||
various
|
r15168 | lfutil.updatestandin(self, standin) | ||
lfdirstate.normal(lfile) | ||||
else: | ||||
Na'Tosha Bard
|
r15224 | lfdirstate.drop(lfile) | ||
various
|
r15168 | |||
# 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. | ||||
match = copy.copy(match) | ||||
Na'Tosha Bard
|
r16247 | origmatchfn = match.matchfn | ||
various
|
r15168 | |||
Greg Ward
|
r15254 | # Check both the list of largefiles and the list of | ||
# standins because if a largefile was removed, it | ||||
# won't be in the list of largefiles at this point | ||||
various
|
r15168 | match._files += sorted(standins) | ||
actualfiles = [] | ||||
for f in match._files: | ||||
fstandin = lfutil.standin(f) | ||||
Greg Ward
|
r15252 | # ignore known largefiles and standins | ||
various
|
r15168 | if f in lfiles or fstandin in standins: | ||
continue | ||||
Greg Ward
|
r15252 | # append directory separator to avoid collisions | ||
various
|
r15168 | if not fstandin.endswith(os.sep): | ||
fstandin += os.sep | ||||
actualfiles.append(f) | ||||
match._files = actualfiles | ||||
def matchfn(f): | ||||
Na'Tosha Bard
|
r16247 | if origmatchfn(f): | ||
various
|
r15168 | return f not in lfiles | ||
else: | ||||
return f in standins | ||||
match.matchfn = matchfn | ||||
Levi Bard
|
r15794 | result = orig(text=text, user=user, date=date, match=match, | ||
various
|
r15168 | force=force, editor=editor, extra=extra) | ||
Levi Bard
|
r15794 | # This needs to be after commit; otherwise precommit hooks | ||
# get the wrong status | ||||
lfdirstate.write() | ||||
return result | ||||
various
|
r15168 | finally: | ||
wlock.release() | ||||
def push(self, remote, force=False, revs=None, newbranch=False): | ||||
FUJIWARA Katsunori
|
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))) | ||||
raise util.Abort(msg) | ||||
Long Vu
|
r20177 | return super(lfilesrepo, self).push(remote, force=force, revs=revs, | ||
newbranch=newbranch) | ||||
various
|
r15168 | |||
Levi Bard
|
r18064 | def _subdirlfs(self, files, lfiles): | ||
''' | ||||
Adjust matched file list | ||||
If we pass a directory to commit whose only commitable files | ||||
are largefiles, the core commit code aborts before finding | ||||
the largefiles. | ||||
So we do the following: | ||||
For directories that only have largefiles as matches, | ||||
Mads Kiilerich
|
r18644 | we explicitly add the largefiles to the match list and remove | ||
Levi Bard
|
r18064 | the directory. | ||
In other cases, we leave the match list unmodified. | ||||
''' | ||||
actualfiles = [] | ||||
dirs = [] | ||||
regulars = [] | ||||
for f in files: | ||||
if lfutil.isstandin(f + '/'): | ||||
raise util.Abort( | ||||
_('file "%s" is a largefile standin') % f, | ||||
hint=('commit the largefile itself instead')) | ||||
# Scan directories | ||||
if os.path.isdir(self.wjoin(f)): | ||||
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: | ||||
actualfiles.append(lfutil.standin(f)) | ||||
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 | ||||
Na'Tosha Bard
|
r16247 | repo.__class__ = lfilesrepo | ||
various
|
r15168 | |||
FUJIWARA Katsunori
|
r21044 | def prepushoutgoinghook(local, remote, outgoing): | ||
if outgoing.missing: | ||||
toupload = set() | ||||
addfunc = lambda fn, lfhash: toupload.add(lfhash) | ||||
lfutil.getlfilestoupload(local, outgoing.missing, addfunc) | ||||
lfcommands.uploadlfiles(ui, local, remote, toupload) | ||||
repo.prepushoutgoinghooks.add("largefiles", prepushoutgoinghook) | ||||
various
|
r15168 | def checkrequireslfiles(ui, repo, **kwargs): | ||
Benjamin Pollack
|
r15319 | if 'largefiles' not in repo.requirements and util.any( | ||
various
|
r15168 | lfutil.shortname+'/' in f[0] for f in repo.store.datafiles()): | ||
Eli Carter
|
r15312 | repo.requirements.add('largefiles') | ||
various
|
r15168 | repo._writerequirements() | ||
Mads Kiilerich
|
r20790 | ui.setconfig('hooks', 'changegroup.lfiles', checkrequireslfiles, | ||
'largefiles') | ||||
ui.setconfig('hooks', 'commit.lfiles', checkrequireslfiles, 'largefiles') | ||||