##// END OF EJS Templates
largefiles: don't close the fd passed to store._getfile
Mads Kiilerich -
r19003:ad993cb7 default
parent child Browse files
Show More
@@ -1,200 +1,200 b''
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
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 import re
12
12
13 from mercurial import util, node, hg
13 from mercurial import util, node, hg
14 from mercurial.i18n import _
14 from mercurial.i18n import _
15
15
16 import lfutil
16 import lfutil
17
17
18 class StoreError(Exception):
18 class StoreError(Exception):
19 '''Raised when there is a problem getting files from or putting
19 '''Raised when there is a problem getting files from or putting
20 files to a central store.'''
20 files to a central store.'''
21 def __init__(self, filename, hash, url, detail):
21 def __init__(self, filename, hash, url, detail):
22 self.filename = filename
22 self.filename = filename
23 self.hash = hash
23 self.hash = hash
24 self.url = url
24 self.url = url
25 self.detail = detail
25 self.detail = detail
26
26
27 def longmessage(self):
27 def longmessage(self):
28 return (_("error getting id %s from url %s for file %s: %s\n") %
28 return (_("error getting id %s from url %s for file %s: %s\n") %
29 (self.hash, self.url, self.filename, self.detail))
29 (self.hash, self.url, self.filename, self.detail))
30
30
31 def __str__(self):
31 def __str__(self):
32 return "%s: %s" % (self.url, self.detail)
32 return "%s: %s" % (self.url, self.detail)
33
33
34 class basestore(object):
34 class basestore(object):
35 def __init__(self, ui, repo, url):
35 def __init__(self, ui, repo, url):
36 self.ui = ui
36 self.ui = ui
37 self.repo = repo
37 self.repo = repo
38 self.url = url
38 self.url = url
39
39
40 def put(self, source, hash):
40 def put(self, source, hash):
41 '''Put source file into the store under <filename>/<hash>.'''
41 '''Put source file into the store under <filename>/<hash>.'''
42 raise NotImplementedError('abstract method')
42 raise NotImplementedError('abstract method')
43
43
44 def exists(self, hashes):
44 def exists(self, hashes):
45 '''Check to see if the store contains the given hashes. Given an
45 '''Check to see if the store contains the given hashes. Given an
46 iterable of hashes it returns a mapping from hash to bool.'''
46 iterable of hashes it returns a mapping from hash to bool.'''
47 raise NotImplementedError('abstract method')
47 raise NotImplementedError('abstract method')
48
48
49 def get(self, files):
49 def get(self, files):
50 '''Get the specified largefiles from the store and write to local
50 '''Get the specified largefiles from the store and write to local
51 files under repo.root. files is a list of (filename, hash)
51 files under repo.root. files is a list of (filename, hash)
52 tuples. Return (success, missing), lists of files successfully
52 tuples. Return (success, missing), lists of files successfully
53 downloaded and those not found in the store. success is a list
53 downloaded and those not found in the store. success is a list
54 of (filename, hash) tuples; missing is a list of filenames that
54 of (filename, hash) tuples; missing is a list of filenames that
55 we could not get. (The detailed error message will already have
55 we could not get. (The detailed error message will already have
56 been presented to the user, so missing is just supplied as a
56 been presented to the user, so missing is just supplied as a
57 summary.)'''
57 summary.)'''
58 success = []
58 success = []
59 missing = []
59 missing = []
60 ui = self.ui
60 ui = self.ui
61
61
62 util.makedirs(lfutil.storepath(self.repo, ''))
62 util.makedirs(lfutil.storepath(self.repo, ''))
63
63
64 at = 0
64 at = 0
65 for filename, hash in files:
65 for filename, hash in files:
66 ui.progress(_('getting largefiles'), at, unit='lfile',
66 ui.progress(_('getting largefiles'), at, unit='lfile',
67 total=len(files))
67 total=len(files))
68 at += 1
68 at += 1
69 ui.note(_('getting %s:%s\n') % (filename, hash))
69 ui.note(_('getting %s:%s\n') % (filename, hash))
70
70
71 storefilename = lfutil.storepath(self.repo, hash)
71 storefilename = lfutil.storepath(self.repo, hash)
72 tmpfile = util.atomictempfile(storefilename + '.tmp',
72 tmpfile = util.atomictempfile(storefilename + '.tmp',
73 createmode=self.repo.store.createmode)
73 createmode=self.repo.store.createmode)
74
74
75 try:
75 try:
76 hhash = self._getfile(tmpfile, filename, hash)
76 hhash = self._getfile(tmpfile, filename, hash)
77 except StoreError, err:
77 except StoreError, err:
78 ui.warn(err.longmessage())
78 ui.warn(err.longmessage())
79 hhash = ""
79 hhash = ""
80 tmpfile.close() # has probably already been closed!
80 tmpfile.close()
81
81
82 if hhash != hash:
82 if hhash != hash:
83 if hhash != "":
83 if hhash != "":
84 ui.warn(_('%s: data corruption (expected %s, got %s)\n')
84 ui.warn(_('%s: data corruption (expected %s, got %s)\n')
85 % (filename, hash, hhash))
85 % (filename, hash, hhash))
86 util.unlink(storefilename + '.tmp')
86 util.unlink(storefilename + '.tmp')
87 missing.append(filename)
87 missing.append(filename)
88 continue
88 continue
89
89
90 util.rename(storefilename + '.tmp', storefilename)
90 util.rename(storefilename + '.tmp', storefilename)
91 lfutil.linktousercache(self.repo, hash)
91 lfutil.linktousercache(self.repo, hash)
92 success.append((filename, hhash))
92 success.append((filename, hhash))
93
93
94 ui.progress(_('getting largefiles'), None)
94 ui.progress(_('getting largefiles'), None)
95 return (success, missing)
95 return (success, missing)
96
96
97 def verify(self, revs, contents=False):
97 def verify(self, revs, contents=False):
98 '''Verify the existence (and, optionally, contents) of every big
98 '''Verify the existence (and, optionally, contents) of every big
99 file revision referenced by every changeset in revs.
99 file revision referenced by every changeset in revs.
100 Return 0 if all is well, non-zero on any errors.'''
100 Return 0 if all is well, non-zero on any errors.'''
101 failed = False
101 failed = False
102
102
103 self.ui.status(_('searching %d changesets for largefiles\n') %
103 self.ui.status(_('searching %d changesets for largefiles\n') %
104 len(revs))
104 len(revs))
105 verified = set() # set of (filename, filenode) tuples
105 verified = set() # set of (filename, filenode) tuples
106
106
107 for rev in revs:
107 for rev in revs:
108 cctx = self.repo[rev]
108 cctx = self.repo[rev]
109 cset = "%d:%s" % (cctx.rev(), node.short(cctx.node()))
109 cset = "%d:%s" % (cctx.rev(), node.short(cctx.node()))
110
110
111 for standin in cctx:
111 for standin in cctx:
112 if self._verifyfile(cctx, cset, contents, standin, verified):
112 if self._verifyfile(cctx, cset, contents, standin, verified):
113 failed = True
113 failed = True
114
114
115 numrevs = len(verified)
115 numrevs = len(verified)
116 numlfiles = len(set([fname for (fname, fnode) in verified]))
116 numlfiles = len(set([fname for (fname, fnode) in verified]))
117 if contents:
117 if contents:
118 self.ui.status(
118 self.ui.status(
119 _('verified contents of %d revisions of %d largefiles\n')
119 _('verified contents of %d revisions of %d largefiles\n')
120 % (numrevs, numlfiles))
120 % (numrevs, numlfiles))
121 else:
121 else:
122 self.ui.status(
122 self.ui.status(
123 _('verified existence of %d revisions of %d largefiles\n')
123 _('verified existence of %d revisions of %d largefiles\n')
124 % (numrevs, numlfiles))
124 % (numrevs, numlfiles))
125 return int(failed)
125 return int(failed)
126
126
127 def _getfile(self, tmpfile, filename, hash):
127 def _getfile(self, tmpfile, filename, hash):
128 '''Fetch one revision of one file from the store and write it
128 '''Fetch one revision of one file from the store and write it
129 to tmpfile. Compute the hash of the file on-the-fly as it
129 to tmpfile. Compute the hash of the file on-the-fly as it
130 downloads and return the hash. Close tmpfile. Raise
130 downloads and return the hash. Close tmpfile. Raise
131 StoreError if unable to download the file (e.g. it does not
131 StoreError if unable to download the file (e.g. it does not
132 exist in the store).'''
132 exist in the store).'''
133 raise NotImplementedError('abstract method')
133 raise NotImplementedError('abstract method')
134
134
135 def _verifyfile(self, cctx, cset, contents, standin, verified):
135 def _verifyfile(self, cctx, cset, contents, standin, verified):
136 '''Perform the actual verification of a file in the store.
136 '''Perform the actual verification of a file in the store.
137 'cset' is only used in warnings.
137 'cset' is only used in warnings.
138 'contents' controls verification of content hash.
138 'contents' controls verification of content hash.
139 'standin' is the standin path of the largefile to verify.
139 'standin' is the standin path of the largefile to verify.
140 'verified' is maintained as a set of already verified files.
140 'verified' is maintained as a set of already verified files.
141 Returns _true_ if it is a standin and any problems are found!
141 Returns _true_ if it is a standin and any problems are found!
142 '''
142 '''
143 raise NotImplementedError('abstract method')
143 raise NotImplementedError('abstract method')
144
144
145 import localstore, wirestore
145 import localstore, wirestore
146
146
147 _storeprovider = {
147 _storeprovider = {
148 'file': [localstore.localstore],
148 'file': [localstore.localstore],
149 'http': [wirestore.wirestore],
149 'http': [wirestore.wirestore],
150 'https': [wirestore.wirestore],
150 'https': [wirestore.wirestore],
151 'ssh': [wirestore.wirestore],
151 'ssh': [wirestore.wirestore],
152 }
152 }
153
153
154 _scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
154 _scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
155
155
156 # During clone this function is passed the src's ui object
156 # During clone this function is passed the src's ui object
157 # but it needs the dest's ui object so it can read out of
157 # but it needs the dest's ui object so it can read out of
158 # the config file. Use repo.ui instead.
158 # the config file. Use repo.ui instead.
159 def _openstore(repo, remote=None, put=False):
159 def _openstore(repo, remote=None, put=False):
160 ui = repo.ui
160 ui = repo.ui
161
161
162 if not remote:
162 if not remote:
163 lfpullsource = getattr(repo, 'lfpullsource', None)
163 lfpullsource = getattr(repo, 'lfpullsource', None)
164 if lfpullsource:
164 if lfpullsource:
165 path = ui.expandpath(lfpullsource)
165 path = ui.expandpath(lfpullsource)
166 else:
166 else:
167 path = ui.expandpath('default-push', 'default')
167 path = ui.expandpath('default-push', 'default')
168
168
169 # ui.expandpath() leaves 'default-push' and 'default' alone if
169 # ui.expandpath() leaves 'default-push' and 'default' alone if
170 # they cannot be expanded: fallback to the empty string,
170 # they cannot be expanded: fallback to the empty string,
171 # meaning the current directory.
171 # meaning the current directory.
172 if path == 'default-push' or path == 'default':
172 if path == 'default-push' or path == 'default':
173 path = ''
173 path = ''
174 remote = repo
174 remote = repo
175 else:
175 else:
176 path, _branches = hg.parseurl(path)
176 path, _branches = hg.parseurl(path)
177 remote = hg.peer(repo, {}, path)
177 remote = hg.peer(repo, {}, path)
178
178
179 # The path could be a scheme so use Mercurial's normal functionality
179 # The path could be a scheme so use Mercurial's normal functionality
180 # to resolve the scheme to a repository and use its path
180 # to resolve the scheme to a repository and use its path
181 path = util.safehasattr(remote, 'url') and remote.url() or remote.path
181 path = util.safehasattr(remote, 'url') and remote.url() or remote.path
182
182
183 match = _scheme_re.match(path)
183 match = _scheme_re.match(path)
184 if not match: # regular filesystem path
184 if not match: # regular filesystem path
185 scheme = 'file'
185 scheme = 'file'
186 else:
186 else:
187 scheme = match.group(1)
187 scheme = match.group(1)
188
188
189 try:
189 try:
190 storeproviders = _storeprovider[scheme]
190 storeproviders = _storeprovider[scheme]
191 except KeyError:
191 except KeyError:
192 raise util.Abort(_('unsupported URL scheme %r') % scheme)
192 raise util.Abort(_('unsupported URL scheme %r') % scheme)
193
193
194 for classobj in storeproviders:
194 for classobj in storeproviders:
195 try:
195 try:
196 return classobj(ui, repo, remote)
196 return classobj(ui, repo, remote)
197 except lfutil.storeprotonotcapable:
197 except lfutil.storeprotonotcapable:
198 pass
198 pass
199
199
200 raise util.Abort(_('%s does not appear to be a largefile store') % path)
200 raise util.Abort(_('%s does not appear to be a largefile store') % path)
@@ -1,75 +1,74 b''
1 # Copyright 2009-2010 Gregory P. Ward
1 # Copyright 2009-2010 Gregory P. Ward
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 # Copyright 2010-2011 Fog Creek Software
3 # Copyright 2010-2011 Fog Creek Software
4 # Copyright 2010-2011 Unity Technologies
4 # Copyright 2010-2011 Unity Technologies
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 '''store class for local filesystem'''
9 '''store class for local filesystem'''
10
10
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12
12
13 import lfutil
13 import lfutil
14 import basestore
14 import basestore
15
15
16 class localstore(basestore.basestore):
16 class localstore(basestore.basestore):
17 '''localstore first attempts to grab files out of the store in the remote
17 '''localstore first attempts to grab files out of the store in the remote
18 Mercurial repository. Failing that, it attempts to grab the files from
18 Mercurial repository. Failing that, it attempts to grab the files from
19 the user cache.'''
19 the user cache.'''
20
20
21 def __init__(self, ui, repo, remote):
21 def __init__(self, ui, repo, remote):
22 self.remote = remote.local()
22 self.remote = remote.local()
23 super(localstore, self).__init__(ui, repo, self.remote.url())
23 super(localstore, self).__init__(ui, repo, self.remote.url())
24
24
25 def put(self, source, hash):
25 def put(self, source, hash):
26 if lfutil.instore(self.remote, hash):
26 if lfutil.instore(self.remote, hash):
27 return
27 return
28 lfutil.link(lfutil.storepath(self.repo, hash),
28 lfutil.link(lfutil.storepath(self.repo, hash),
29 lfutil.storepath(self.remote, hash))
29 lfutil.storepath(self.remote, hash))
30
30
31 def exists(self, hashes):
31 def exists(self, hashes):
32 retval = {}
32 retval = {}
33 for hash in hashes:
33 for hash in hashes:
34 retval[hash] = lfutil.instore(self.remote, hash)
34 retval[hash] = lfutil.instore(self.remote, hash)
35 return retval
35 return retval
36
36
37
37
38 def _getfile(self, tmpfile, filename, hash):
38 def _getfile(self, tmpfile, filename, hash):
39 path = lfutil.findfile(self.remote, hash)
39 path = lfutil.findfile(self.remote, hash)
40 if not path:
40 if not path:
41 raise basestore.StoreError(filename, hash, self.url,
41 raise basestore.StoreError(filename, hash, self.url,
42 _("can't get file locally"))
42 _("can't get file locally"))
43 fd = open(path, 'rb')
43 fd = open(path, 'rb')
44 try:
44 try:
45 return lfutil.copyandhash(fd, tmpfile)
45 return lfutil.copyandhash(fd, tmpfile)
46 finally:
46 finally:
47 fd.close()
47 fd.close()
48 tmpfile.close()
49
48
50 def _verifyfile(self, cctx, cset, contents, standin, verified):
49 def _verifyfile(self, cctx, cset, contents, standin, verified):
51 filename = lfutil.splitstandin(standin)
50 filename = lfutil.splitstandin(standin)
52 if not filename:
51 if not filename:
53 return False
52 return False
54 fctx = cctx[standin]
53 fctx = cctx[standin]
55 key = (filename, fctx.filenode())
54 key = (filename, fctx.filenode())
56 if key in verified:
55 if key in verified:
57 return False
56 return False
58
57
59 expecthash = fctx.data()[0:40]
58 expecthash = fctx.data()[0:40]
60 storepath = lfutil.storepath(self.remote, expecthash)
59 storepath = lfutil.storepath(self.remote, expecthash)
61 verified.add(key)
60 verified.add(key)
62 if not lfutil.instore(self.remote, expecthash):
61 if not lfutil.instore(self.remote, expecthash):
63 self.ui.warn(
62 self.ui.warn(
64 _('changeset %s: %s references missing %s\n')
63 _('changeset %s: %s references missing %s\n')
65 % (cset, filename, storepath))
64 % (cset, filename, storepath))
66 return True # failed
65 return True # failed
67
66
68 if contents:
67 if contents:
69 actualhash = lfutil.hashfile(storepath)
68 actualhash = lfutil.hashfile(storepath)
70 if actualhash != expecthash:
69 if actualhash != expecthash:
71 self.ui.warn(
70 self.ui.warn(
72 _('changeset %s: %s references corrupted %s\n')
71 _('changeset %s: %s references corrupted %s\n')
73 % (cset, filename, storepath))
72 % (cset, filename, storepath))
74 return True # failed
73 return True # failed
75 return False
74 return False
@@ -1,116 +1,115 b''
1 # Copyright 2010-2011 Fog Creek Software
1 # Copyright 2010-2011 Fog Creek Software
2 # Copyright 2010-2011 Unity Technologies
2 # Copyright 2010-2011 Unity Technologies
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 '''remote largefile store; the base class for wirestore'''
7 '''remote largefile store; the base class for wirestore'''
8
8
9 import urllib2
9 import urllib2
10
10
11 from mercurial import util
11 from mercurial import util
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from mercurial.wireproto import remotebatch
13 from mercurial.wireproto import remotebatch
14
14
15 import lfutil
15 import lfutil
16 import basestore
16 import basestore
17
17
18 class remotestore(basestore.basestore):
18 class remotestore(basestore.basestore):
19 '''a largefile store accessed over a network'''
19 '''a largefile store accessed over a network'''
20 def __init__(self, ui, repo, url):
20 def __init__(self, ui, repo, url):
21 super(remotestore, self).__init__(ui, repo, url)
21 super(remotestore, self).__init__(ui, repo, url)
22
22
23 def put(self, source, hash):
23 def put(self, source, hash):
24 if self.sendfile(source, hash):
24 if self.sendfile(source, hash):
25 raise util.Abort(
25 raise util.Abort(
26 _('remotestore: could not put %s to remote store %s')
26 _('remotestore: could not put %s to remote store %s')
27 % (source, self.url))
27 % (source, self.url))
28 self.ui.debug(
28 self.ui.debug(
29 _('remotestore: put %s to remote store %s') % (source, self.url))
29 _('remotestore: put %s to remote store %s') % (source, self.url))
30
30
31 def exists(self, hashes):
31 def exists(self, hashes):
32 return dict((h, s == 0) for (h, s) in self._stat(hashes).iteritems())
32 return dict((h, s == 0) for (h, s) in self._stat(hashes).iteritems())
33
33
34 def sendfile(self, filename, hash):
34 def sendfile(self, filename, hash):
35 self.ui.debug('remotestore: sendfile(%s, %s)\n' % (filename, hash))
35 self.ui.debug('remotestore: sendfile(%s, %s)\n' % (filename, hash))
36 fd = None
36 fd = None
37 try:
37 try:
38 try:
38 try:
39 fd = lfutil.httpsendfile(self.ui, filename)
39 fd = lfutil.httpsendfile(self.ui, filename)
40 except IOError, e:
40 except IOError, e:
41 raise util.Abort(
41 raise util.Abort(
42 _('remotestore: could not open file %s: %s')
42 _('remotestore: could not open file %s: %s')
43 % (filename, str(e)))
43 % (filename, str(e)))
44 return self._put(hash, fd)
44 return self._put(hash, fd)
45 finally:
45 finally:
46 if fd:
46 if fd:
47 fd.close()
47 fd.close()
48
48
49 def _getfile(self, tmpfile, filename, hash):
49 def _getfile(self, tmpfile, filename, hash):
50 # quit if the largefile isn't there
50 # quit if the largefile isn't there
51 stat = self._stat([hash])[hash]
51 stat = self._stat([hash])[hash]
52 if stat == 1:
52 if stat == 1:
53 raise util.Abort(_('remotestore: largefile %s is invalid') % hash)
53 raise util.Abort(_('remotestore: largefile %s is invalid') % hash)
54 elif stat == 2:
54 elif stat == 2:
55 raise util.Abort(_('remotestore: largefile %s is missing') % hash)
55 raise util.Abort(_('remotestore: largefile %s is missing') % hash)
56 elif stat != 0:
56 elif stat != 0:
57 raise RuntimeError('error getting file: unexpected response from '
57 raise RuntimeError('error getting file: unexpected response from '
58 'statlfile (%r)' % stat)
58 'statlfile (%r)' % stat)
59
59
60 try:
60 try:
61 length, infile = self._get(hash)
61 length, infile = self._get(hash)
62 except urllib2.HTTPError, e:
62 except urllib2.HTTPError, e:
63 # 401s get converted to util.Aborts; everything else is fine being
63 # 401s get converted to util.Aborts; everything else is fine being
64 # turned into a StoreError
64 # turned into a StoreError
65 raise basestore.StoreError(filename, hash, self.url, str(e))
65 raise basestore.StoreError(filename, hash, self.url, str(e))
66 except urllib2.URLError, e:
66 except urllib2.URLError, e:
67 # This usually indicates a connection problem, so don't
67 # This usually indicates a connection problem, so don't
68 # keep trying with the other files... they will probably
68 # keep trying with the other files... they will probably
69 # all fail too.
69 # all fail too.
70 raise util.Abort('%s: %s' % (self.url, e.reason))
70 raise util.Abort('%s: %s' % (self.url, e.reason))
71 except IOError, e:
71 except IOError, e:
72 raise basestore.StoreError(filename, hash, self.url, str(e))
72 raise basestore.StoreError(filename, hash, self.url, str(e))
73
73
74 # Mercurial does not close its SSH connections after writing a stream
74 # Mercurial does not close its SSH connections after writing a stream
75 if length is not None:
75 if length is not None:
76 infile = lfutil.limitreader(infile, length)
76 infile = lfutil.limitreader(infile, length)
77 try:
77 try:
78 return lfutil.copyandhash(util.filechunkiter(infile, 128 * 1024),
78 return lfutil.copyandhash(util.filechunkiter(infile, 128 * 1024),
79 tmpfile)
79 tmpfile)
80 finally:
80 finally:
81 infile.close()
81 infile.close()
82 tmpfile.close()
83
82
84 def _verifyfile(self, cctx, cset, contents, standin, verified):
83 def _verifyfile(self, cctx, cset, contents, standin, verified):
85 filename = lfutil.splitstandin(standin)
84 filename = lfutil.splitstandin(standin)
86 if not filename:
85 if not filename:
87 return False
86 return False
88 fctx = cctx[standin]
87 fctx = cctx[standin]
89 key = (filename, fctx.filenode())
88 key = (filename, fctx.filenode())
90 if key in verified:
89 if key in verified:
91 return False
90 return False
92
91
93 verified.add(key)
92 verified.add(key)
94
93
95 expecthash = fctx.data()[0:40]
94 expecthash = fctx.data()[0:40]
96 stat = self._stat([expecthash])[expecthash]
95 stat = self._stat([expecthash])[expecthash]
97 if not stat:
96 if not stat:
98 return False
97 return False
99 elif stat == 1:
98 elif stat == 1:
100 self.ui.warn(
99 self.ui.warn(
101 _('changeset %s: %s: contents differ\n')
100 _('changeset %s: %s: contents differ\n')
102 % (cset, filename))
101 % (cset, filename))
103 return True # failed
102 return True # failed
104 elif stat == 2:
103 elif stat == 2:
105 self.ui.warn(
104 self.ui.warn(
106 _('changeset %s: %s missing\n')
105 _('changeset %s: %s missing\n')
107 % (cset, filename))
106 % (cset, filename))
108 return True # failed
107 return True # failed
109 else:
108 else:
110 raise RuntimeError('verify failed: unexpected response from '
109 raise RuntimeError('verify failed: unexpected response from '
111 'statlfile (%r)' % stat)
110 'statlfile (%r)' % stat)
112
111
113 def batch(self):
112 def batch(self):
114 '''Support for remote batching.'''
113 '''Support for remote batching.'''
115 return remotebatch(self)
114 return remotebatch(self)
116
115
General Comments 0
You need to be logged in to leave comments. Login now