##// END OF EJS Templates
largefiles: move basestore._openstore into new module to remove cycle
liscju -
r29305:814076f4 default
parent child Browse files
Show More
@@ -8,9 +8,7
8
8
9 '''base class for store implementations and store-related utility code'''
9 '''base class for store implementations and store-related utility code'''
10
10
11 import re
11 from mercurial import util, node
12
13 from mercurial import util, node, hg, error
14 from mercurial.i18n import _
12 from mercurial.i18n import _
15
13
16 import lfutil
14 import lfutil
@@ -164,63 +162,3 class basestore(object):
164 Returns _true_ if any problems are found!
162 Returns _true_ if any problems are found!
165 '''
163 '''
166 raise NotImplementedError('abstract method')
164 raise NotImplementedError('abstract method')
167
168 import localstore, wirestore
169
170 _storeprovider = {
171 'file': [localstore.localstore],
172 'http': [wirestore.wirestore],
173 'https': [wirestore.wirestore],
174 'ssh': [wirestore.wirestore],
175 }
176
177 _scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
178
179 # During clone this function is passed the src's ui object
180 # but it needs the dest's ui object so it can read out of
181 # the config file. Use repo.ui instead.
182 def _openstore(repo, remote=None, put=False):
183 ui = repo.ui
184
185 if not remote:
186 lfpullsource = getattr(repo, 'lfpullsource', None)
187 if lfpullsource:
188 path = ui.expandpath(lfpullsource)
189 elif put:
190 path = ui.expandpath('default-push', 'default')
191 else:
192 path = ui.expandpath('default')
193
194 # ui.expandpath() leaves 'default-push' and 'default' alone if
195 # they cannot be expanded: fallback to the empty string,
196 # meaning the current directory.
197 if path == 'default-push' or path == 'default':
198 path = ''
199 remote = repo
200 else:
201 path, _branches = hg.parseurl(path)
202 remote = hg.peer(repo, {}, path)
203
204 # The path could be a scheme so use Mercurial's normal functionality
205 # to resolve the scheme to a repository and use its path
206 path = util.safehasattr(remote, 'url') and remote.url() or remote.path
207
208 match = _scheme_re.match(path)
209 if not match: # regular filesystem path
210 scheme = 'file'
211 else:
212 scheme = match.group(1)
213
214 try:
215 storeproviders = _storeprovider[scheme]
216 except KeyError:
217 raise error.Abort(_('unsupported URL scheme %r') % scheme)
218
219 for classobj in storeproviders:
220 try:
221 return classobj(ui, repo, remote)
222 except lfutil.storeprotonotcapable:
223 pass
224
225 raise error.Abort(_('%s does not appear to be a largefile store') %
226 util.hidepassword(path))
@@ -20,7 +20,7 from hgext.convert import convcmd
20 from hgext.convert import filemap
20 from hgext.convert import filemap
21
21
22 import lfutil
22 import lfutil
23 import basestore
23 import storefactory
24
24
25 # -- Commands ----------------------------------------------------------
25 # -- Commands ----------------------------------------------------------
26
26
@@ -337,7 +337,7 def uploadlfiles(ui, rsrc, rdst, files):
337 if not files:
337 if not files:
338 return
338 return
339
339
340 store = basestore._openstore(rsrc, rdst, put=True)
340 store = storefactory._openstore(rsrc, rdst, put=True)
341
341
342 at = 0
342 at = 0
343 ui.debug("sending statlfile command for %d largefiles\n" % len(files))
343 ui.debug("sending statlfile command for %d largefiles\n" % len(files))
@@ -368,7 +368,7 def verifylfiles(ui, repo, all=False, co
368 else:
368 else:
369 revs = ['.']
369 revs = ['.']
370
370
371 store = basestore._openstore(repo)
371 store = storefactory._openstore(repo)
372 return store.verify(revs, contents=contents)
372 return store.verify(revs, contents=contents)
373
373
374 def cachelfiles(ui, repo, node, filelist=None):
374 def cachelfiles(ui, repo, node, filelist=None):
@@ -394,7 +394,7 def cachelfiles(ui, repo, node, filelist
394 toget.append((lfile, expectedhash))
394 toget.append((lfile, expectedhash))
395
395
396 if toget:
396 if toget:
397 store = basestore._openstore(repo)
397 store = storefactory._openstore(repo)
398 ret = store.get(toget)
398 ret = store.get(toget)
399 return ret
399 return ret
400
400
@@ -17,7 +17,7 from mercurial.i18n import _
17
17
18 import lfutil
18 import lfutil
19 import lfcommands
19 import lfcommands
20 import basestore
20 import storefactory
21
21
22 # -- Utility functions: commonly/repeatedly needed functionality ---------------
22 # -- Utility functions: commonly/repeatedly needed functionality ---------------
23
23
@@ -1109,7 +1109,7 def _getoutgoings(repo, other, missing,
1109 lfhashes.add(lfhash)
1109 lfhashes.add(lfhash)
1110 lfutil.getlfilestoupload(repo, missing, dedup)
1110 lfutil.getlfilestoupload(repo, missing, dedup)
1111 if lfhashes:
1111 if lfhashes:
1112 lfexists = basestore._openstore(repo, other).exists(lfhashes)
1112 lfexists = storefactory._openstore(repo, other).exists(lfhashes)
1113 for fn, lfhash in knowns:
1113 for fn, lfhash in knowns:
1114 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1114 if not lfexists[lfhash]: # lfhash doesn't exist on "other"
1115 addfunc(fn, lfhash)
1115 addfunc(fn, lfhash)
@@ -1338,7 +1338,7 def overridecat(orig, ui, repo, file1, *
1338 else:
1338 else:
1339 hash = lfutil.readstandin(repo, lf, ctx.rev())
1339 hash = lfutil.readstandin(repo, lf, ctx.rev())
1340 if not lfutil.inusercache(repo.ui, hash):
1340 if not lfutil.inusercache(repo.ui, hash):
1341 store = basestore._openstore(repo)
1341 store = storefactory._openstore(repo)
1342 success, missing = store.get([(lf, hash)])
1342 success, missing = store.get([(lf, hash)])
1343 if len(success) != 1:
1343 if len(success) != 1:
1344 raise error.Abort(
1344 raise error.Abort(
@@ -1,180 +1,23
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
5 #
6 # This software may be used and distributed according to the terms of the
1 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
2 # GNU General Public License version 2 or any later version.
8
3
9 '''base class for store implementations and store-related utility code'''
4 from __future__ import absolute_import
10
5
11 import re
6 import re
12
7
13 from mercurial import util, node, hg, error
14 from mercurial.i18n import _
8 from mercurial.i18n import _
15
9
16 import lfutil
10 from mercurial import (
17
11 error,
18 class StoreError(Exception):
12 hg,
19 '''Raised when there is a problem getting files from or putting
13 util,
20 files to a central store.'''
14 )
21 def __init__(self, filename, hash, url, detail):
22 self.filename = filename
23 self.hash = hash
24 self.url = url
25 self.detail = detail
26
27 def longmessage(self):
28 return (_("error getting id %s from url %s for file %s: %s\n") %
29 (self.hash, util.hidepassword(self.url), self.filename,
30 self.detail))
31
32 def __str__(self):
33 return "%s: %s" % (util.hidepassword(self.url), self.detail)
34
35 class basestore(object):
36 def __init__(self, ui, repo, url):
37 self.ui = ui
38 self.repo = repo
39 self.url = url
40
41 def put(self, source, hash):
42 '''Put source file into the store so it can be retrieved by hash.'''
43 raise NotImplementedError('abstract method')
44
45 def exists(self, hashes):
46 '''Check to see if the store contains the given hashes. Given an
47 iterable of hashes it returns a mapping from hash to bool.'''
48 raise NotImplementedError('abstract method')
49
50 def get(self, files):
51 '''Get the specified largefiles from the store and write to local
52 files under repo.root. files is a list of (filename, hash)
53 tuples. Return (success, missing), lists of files successfully
54 downloaded and those not found in the store. success is a list
55 of (filename, hash) tuples; missing is a list of filenames that
56 we could not get. (The detailed error message will already have
57 been presented to the user, so missing is just supplied as a
58 summary.)'''
59 success = []
60 missing = []
61 ui = self.ui
62
63 at = 0
64 available = self.exists(set(hash for (_filename, hash) in files))
65 for filename, hash in files:
66 ui.progress(_('getting largefiles'), at, unit=_('files'),
67 total=len(files))
68 at += 1
69 ui.note(_('getting %s:%s\n') % (filename, hash))
70
71 if not available.get(hash):
72 ui.warn(_('%s: largefile %s not available from %s\n')
73 % (filename, hash, util.hidepassword(self.url)))
74 missing.append(filename)
75 continue
76
77 if self._gethash(filename, hash):
78 success.append((filename, hash))
79 else:
80 missing.append(filename)
81
82 ui.progress(_('getting largefiles'), None)
83 return (success, missing)
84
85 def _gethash(self, filename, hash):
86 """Get file with the provided hash and store it in the local repo's
87 store and in the usercache.
88 filename is for informational messages only.
89 """
90 util.makedirs(lfutil.storepath(self.repo, ''))
91 storefilename = lfutil.storepath(self.repo, hash)
92
93 tmpname = storefilename + '.tmp'
94 tmpfile = util.atomictempfile(tmpname,
95 createmode=self.repo.store.createmode)
96
15
97 try:
16 from . import (
98 gothash = self._getfile(tmpfile, filename, hash)
17 lfutil,
99 except StoreError as err:
18 localstore,
100 self.ui.warn(err.longmessage())
19 wirestore,
101 gothash = ""
20 )
102 tmpfile.close()
103
104 if gothash != hash:
105 if gothash != "":
106 self.ui.warn(_('%s: data corruption (expected %s, got %s)\n')
107 % (filename, hash, gothash))
108 util.unlink(tmpname)
109 return False
110
111 util.rename(tmpname, storefilename)
112 lfutil.linktousercache(self.repo, hash)
113 return True
114
115 def verify(self, revs, contents=False):
116 '''Verify the existence (and, optionally, contents) of every big
117 file revision referenced by every changeset in revs.
118 Return 0 if all is well, non-zero on any errors.'''
119
120 self.ui.status(_('searching %d changesets for largefiles\n') %
121 len(revs))
122 verified = set() # set of (filename, filenode) tuples
123 filestocheck = [] # list of (cset, filename, expectedhash)
124 for rev in revs:
125 cctx = self.repo[rev]
126 cset = "%d:%s" % (cctx.rev(), node.short(cctx.node()))
127
128 for standin in cctx:
129 filename = lfutil.splitstandin(standin)
130 if filename:
131 fctx = cctx[standin]
132 key = (filename, fctx.filenode())
133 if key not in verified:
134 verified.add(key)
135 expectedhash = fctx.data()[0:40]
136 filestocheck.append((cset, filename, expectedhash))
137
138 failed = self._verifyfiles(contents, filestocheck)
139
140 numrevs = len(verified)
141 numlfiles = len(set([fname for (fname, fnode) in verified]))
142 if contents:
143 self.ui.status(
144 _('verified contents of %d revisions of %d largefiles\n')
145 % (numrevs, numlfiles))
146 else:
147 self.ui.status(
148 _('verified existence of %d revisions of %d largefiles\n')
149 % (numrevs, numlfiles))
150 return int(failed)
151
152 def _getfile(self, tmpfile, filename, hash):
153 '''Fetch one revision of one file from the store and write it
154 to tmpfile. Compute the hash of the file on-the-fly as it
155 downloads and return the hash. Close tmpfile. Raise
156 StoreError if unable to download the file (e.g. it does not
157 exist in the store).'''
158 raise NotImplementedError('abstract method')
159
160 def _verifyfiles(self, contents, filestocheck):
161 '''Perform the actual verification of files in the store.
162 'contents' controls verification of content hash.
163 'filestocheck' is list of files to check.
164 Returns _true_ if any problems are found!
165 '''
166 raise NotImplementedError('abstract method')
167
168 import localstore, wirestore
169
170 _storeprovider = {
171 'file': [localstore.localstore],
172 'http': [wirestore.wirestore],
173 'https': [wirestore.wirestore],
174 'ssh': [wirestore.wirestore],
175 }
176
177 _scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
178
21
179 # During clone this function is passed the src's ui object
22 # During clone this function is passed the src's ui object
180 # but it needs the dest's ui object so it can read out of
23 # but it needs the dest's ui object so it can read out of
@@ -224,3 +67,12 def _openstore(repo, remote=None, put=Fa
224
67
225 raise error.Abort(_('%s does not appear to be a largefile store') %
68 raise error.Abort(_('%s does not appear to be a largefile store') %
226 util.hidepassword(path))
69 util.hidepassword(path))
70
71 _storeprovider = {
72 'file': [localstore.localstore],
73 'http': [wirestore.wirestore],
74 'https': [wirestore.wirestore],
75 'ssh': [wirestore.wirestore],
76 }
77
78 _scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
@@ -179,5 +179,3 outputs, which should be fixed later.
179 > -X tests/test-hgweb-no-request-uri.t \
179 > -X tests/test-hgweb-no-request-uri.t \
180 > -X tests/test-hgweb-non-interactive.t \
180 > -X tests/test-hgweb-non-interactive.t \
181 > | sed 's-\\-/-g' | python "$import_checker" -
181 > | sed 's-\\-/-g' | python "$import_checker" -
182 Import cycle: hgext.largefiles.basestore -> hgext.largefiles.localstore -> hgext.largefiles.basestore
183 [1]
General Comments 0
You need to be logged in to leave comments. Login now