basestore.py
164 lines
| 6.3 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. | ||||
Greg Ward
|
r15252 | '''base class for store implementations and store-related utility code''' | ||
liscju
|
r29307 | from __future__ import absolute_import | ||
various
|
r15168 | |||
from mercurial.i18n import _ | ||||
liscju
|
r29307 | from mercurial import node, util | ||
from . import lfutil | ||||
various
|
r15168 | |||
class StoreError(Exception): | ||||
'''Raised when there is a problem getting files from or putting | ||||
files to a central store.''' | ||||
def __init__(self, filename, hash, url, detail): | ||||
self.filename = filename | ||||
self.hash = hash | ||||
self.url = url | ||||
self.detail = detail | ||||
def longmessage(self): | ||||
Wagner Bruna
|
r18461 | return (_("error getting id %s from url %s for file %s: %s\n") % | ||
Mads Kiilerich
|
r19950 | (self.hash, util.hidepassword(self.url), self.filename, | ||
self.detail)) | ||||
various
|
r15168 | |||
def __str__(self): | ||||
Mads Kiilerich
|
r19950 | return "%s: %s" % (util.hidepassword(self.url), self.detail) | ||
various
|
r15168 | |||
class basestore(object): | ||||
def __init__(self, ui, repo, url): | ||||
self.ui = ui | ||||
self.repo = repo | ||||
self.url = url | ||||
def put(self, source, hash): | ||||
Mads Kiilerich
|
r19007 | '''Put source file into the store so it can be retrieved by hash.''' | ||
various
|
r15168 | raise NotImplementedError('abstract method') | ||
Na'Tosha Bard
|
r17127 | def exists(self, hashes): | ||
Mads Kiilerich
|
r18573 | '''Check to see if the store contains the given hashes. Given an | ||
iterable of hashes it returns a mapping from hash to bool.''' | ||||
various
|
r15168 | raise NotImplementedError('abstract method') | ||
def get(self, files): | ||||
'''Get the specified largefiles from the store and write to local | ||||
files under repo.root. files is a list of (filename, hash) | ||||
Mads Kiilerich
|
r17424 | tuples. Return (success, missing), lists of files successfully | ||
various
|
r15168 | downloaded and those not found in the store. success is a list | ||
of (filename, hash) tuples; missing is a list of filenames that | ||||
we could not get. (The detailed error message will already have | ||||
been presented to the user, so missing is just supplied as a | ||||
summary.)''' | ||||
success = [] | ||||
missing = [] | ||||
ui = self.ui | ||||
at = 0 | ||||
Mads Kiilerich
|
r19008 | available = self.exists(set(hash for (_filename, hash) in files)) | ||
Matt Harbison
|
r39427 | with ui.makeprogress(_('getting largefiles'), unit=_('files'), | ||
total=len(files)) as progress: | ||||
for filename, hash in files: | ||||
progress.update(at) | ||||
at += 1 | ||||
ui.note(_('getting %s:%s\n') % (filename, hash)) | ||||
various
|
r15168 | |||
Matt Harbison
|
r39427 | if not available.get(hash): | ||
ui.warn(_('%s: largefile %s not available from %s\n') | ||||
% (filename, hash, util.hidepassword(self.url))) | ||||
missing.append(filename) | ||||
continue | ||||
Mads Kiilerich
|
r19008 | |||
Matt Harbison
|
r39427 | if self._gethash(filename, hash): | ||
success.append((filename, hash)) | ||||
else: | ||||
missing.append(filename) | ||||
various
|
r15168 | |||
return (success, missing) | ||||
Mads Kiilerich
|
r19918 | def _gethash(self, filename, hash): | ||
"""Get file with the provided hash and store it in the local repo's | ||||
store and in the usercache. | ||||
filename is for informational messages only. | ||||
""" | ||||
util.makedirs(lfutil.storepath(self.repo, '')) | ||||
storefilename = lfutil.storepath(self.repo, hash) | ||||
tmpname = storefilename + '.tmp' | ||||
Mads Kiilerich
|
r30142 | with util.atomictempfile(tmpname, | ||
createmode=self.repo.store.createmode) as tmpfile: | ||||
try: | ||||
gothash = self._getfile(tmpfile, filename, hash) | ||||
except StoreError as err: | ||||
self.ui.warn(err.longmessage()) | ||||
gothash = "" | ||||
Mads Kiilerich
|
r19918 | |||
if gothash != hash: | ||||
if gothash != "": | ||||
self.ui.warn(_('%s: data corruption (expected %s, got %s)\n') | ||||
% (filename, hash, gothash)) | ||||
util.unlink(tmpname) | ||||
return False | ||||
util.rename(tmpname, storefilename) | ||||
lfutil.linktousercache(self.repo, hash) | ||||
return True | ||||
various
|
r15168 | def verify(self, revs, contents=False): | ||
'''Verify the existence (and, optionally, contents) of every big | ||||
file revision referenced by every changeset in revs. | ||||
Return 0 if all is well, non-zero on any errors.''' | ||||
Mads Kiilerich
|
r18546 | self.ui.status(_('searching %d changesets for largefiles\n') % | ||
len(revs)) | ||||
various
|
r15168 | verified = set() # set of (filename, filenode) tuples | ||
liscju
|
r29067 | filestocheck = [] # list of (cset, filename, expectedhash) | ||
various
|
r15168 | for rev in revs: | ||
cctx = self.repo[rev] | ||||
cset = "%d:%s" % (cctx.rev(), node.short(cctx.node())) | ||||
Mads Kiilerich
|
r18486 | for standin in cctx: | ||
liscju
|
r29067 | filename = lfutil.splitstandin(standin) | ||
if filename: | ||||
fctx = cctx[standin] | ||||
key = (filename, fctx.filenode()) | ||||
if key not in verified: | ||||
verified.add(key) | ||||
FUJIWARA Katsunori
|
r31740 | expectedhash = lfutil.readasstandin(fctx) | ||
liscju
|
r29067 | filestocheck.append((cset, filename, expectedhash)) | ||
failed = self._verifyfiles(contents, filestocheck) | ||||
various
|
r15168 | |||
Na'Tosha Bard
|
r16247 | numrevs = len(verified) | ||
Martin von Zweigbergk
|
r42224 | numlfiles = len({fname for (fname, fnode) in verified}) | ||
various
|
r15168 | if contents: | ||
Mads Kiilerich
|
r18546 | self.ui.status( | ||
_('verified contents of %d revisions of %d largefiles\n') | ||||
% (numrevs, numlfiles)) | ||||
various
|
r15168 | else: | ||
Mads Kiilerich
|
r18546 | self.ui.status( | ||
_('verified existence of %d revisions of %d largefiles\n') | ||||
% (numrevs, numlfiles)) | ||||
various
|
r15168 | return int(failed) | ||
def _getfile(self, tmpfile, filename, hash): | ||||
'''Fetch one revision of one file from the store and write it | ||||
to tmpfile. Compute the hash of the file on-the-fly as it | ||||
Mads Kiilerich
|
r18999 | downloads and return the hash. Close tmpfile. Raise | ||
various
|
r15168 | StoreError if unable to download the file (e.g. it does not | ||
exist in the store).''' | ||||
raise NotImplementedError('abstract method') | ||||
liscju
|
r29067 | def _verifyfiles(self, contents, filestocheck): | ||
'''Perform the actual verification of files in the store. | ||||
Mads Kiilerich
|
r18574 | 'contents' controls verification of content hash. | ||
liscju
|
r29067 | 'filestocheck' is list of files to check. | ||
Returns _true_ if any problems are found! | ||||
various
|
r15168 | ''' | ||
raise NotImplementedError('abstract method') | ||||