##// END OF EJS Templates
largefiles: simplify lfutil.writehash...
Martin Geisler -
r15574:c9328c82 default
parent child Browse files
Show More
@@ -1,461 +1,452 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 '''largefiles utility code: must not import other modules in this package.'''
9 '''largefiles utility code: must not import other modules in this package.'''
10
10
11 import os
11 import os
12 import errno
12 import errno
13 import platform
13 import platform
14 import shutil
14 import shutil
15 import stat
15 import stat
16 import tempfile
16 import tempfile
17
17
18 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
18 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
19 from mercurial.i18n import _
19 from mercurial.i18n import _
20
20
21 shortname = '.hglf'
21 shortname = '.hglf'
22 longname = 'largefiles'
22 longname = 'largefiles'
23
23
24
24
25 # -- Portability wrappers ----------------------------------------------
25 # -- Portability wrappers ----------------------------------------------
26
26
27 def dirstate_walk(dirstate, matcher, unknown=False, ignored=False):
27 def dirstate_walk(dirstate, matcher, unknown=False, ignored=False):
28 return dirstate.walk(matcher, [], unknown, ignored)
28 return dirstate.walk(matcher, [], unknown, ignored)
29
29
30 def repo_add(repo, list):
30 def repo_add(repo, list):
31 add = repo[None].add
31 add = repo[None].add
32 return add(list)
32 return add(list)
33
33
34 def repo_remove(repo, list, unlink=False):
34 def repo_remove(repo, list, unlink=False):
35 def remove(list, unlink):
35 def remove(list, unlink):
36 wlock = repo.wlock()
36 wlock = repo.wlock()
37 try:
37 try:
38 if unlink:
38 if unlink:
39 for f in list:
39 for f in list:
40 try:
40 try:
41 util.unlinkpath(repo.wjoin(f))
41 util.unlinkpath(repo.wjoin(f))
42 except OSError, inst:
42 except OSError, inst:
43 if inst.errno != errno.ENOENT:
43 if inst.errno != errno.ENOENT:
44 raise
44 raise
45 repo[None].forget(list)
45 repo[None].forget(list)
46 finally:
46 finally:
47 wlock.release()
47 wlock.release()
48 return remove(list, unlink=unlink)
48 return remove(list, unlink=unlink)
49
49
50 def repo_forget(repo, list):
50 def repo_forget(repo, list):
51 forget = repo[None].forget
51 forget = repo[None].forget
52 return forget(list)
52 return forget(list)
53
53
54 def findoutgoing(repo, remote, force):
54 def findoutgoing(repo, remote, force):
55 from mercurial import discovery
55 from mercurial import discovery
56 common, _anyinc, _heads = discovery.findcommonincoming(repo,
56 common, _anyinc, _heads = discovery.findcommonincoming(repo,
57 remote, force=force)
57 remote, force=force)
58 return repo.changelog.findmissing(common)
58 return repo.changelog.findmissing(common)
59
59
60 # -- Private worker functions ------------------------------------------
60 # -- Private worker functions ------------------------------------------
61
61
62 def getminsize(ui, assumelfiles, opt, default=10):
62 def getminsize(ui, assumelfiles, opt, default=10):
63 lfsize = opt
63 lfsize = opt
64 if not lfsize and assumelfiles:
64 if not lfsize and assumelfiles:
65 lfsize = ui.config(longname, 'minsize', default=default)
65 lfsize = ui.config(longname, 'minsize', default=default)
66 if lfsize:
66 if lfsize:
67 try:
67 try:
68 lfsize = float(lfsize)
68 lfsize = float(lfsize)
69 except ValueError:
69 except ValueError:
70 raise util.Abort(_('largefiles: size must be number (not %s)\n')
70 raise util.Abort(_('largefiles: size must be number (not %s)\n')
71 % lfsize)
71 % lfsize)
72 if lfsize is None:
72 if lfsize is None:
73 raise util.Abort(_('minimum size for largefiles must be specified'))
73 raise util.Abort(_('minimum size for largefiles must be specified'))
74 return lfsize
74 return lfsize
75
75
76 def link(src, dest):
76 def link(src, dest):
77 try:
77 try:
78 util.oslink(src, dest)
78 util.oslink(src, dest)
79 except OSError:
79 except OSError:
80 # if hardlinks fail, fallback on atomic copy
80 # if hardlinks fail, fallback on atomic copy
81 dst = util.atomictempfile(dest)
81 dst = util.atomictempfile(dest)
82 for chunk in util.filechunkiter(open(src)):
82 for chunk in util.filechunkiter(open(src)):
83 dst.write(chunk)
83 dst.write(chunk)
84 dst.close()
84 dst.close()
85 os.chmod(dest, os.stat(src).st_mode)
85 os.chmod(dest, os.stat(src).st_mode)
86
86
87 def usercachepath(ui, hash):
87 def usercachepath(ui, hash):
88 path = ui.configpath(longname, 'usercache', None)
88 path = ui.configpath(longname, 'usercache', None)
89 if path:
89 if path:
90 path = os.path.join(path, hash)
90 path = os.path.join(path, hash)
91 else:
91 else:
92 if os.name == 'nt':
92 if os.name == 'nt':
93 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
93 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
94 path = os.path.join(appdata, longname, hash)
94 path = os.path.join(appdata, longname, hash)
95 elif platform.system() == 'Darwin':
95 elif platform.system() == 'Darwin':
96 path = os.path.join(os.getenv('HOME'), 'Library', 'Caches',
96 path = os.path.join(os.getenv('HOME'), 'Library', 'Caches',
97 longname, hash)
97 longname, hash)
98 elif os.name == 'posix':
98 elif os.name == 'posix':
99 path = os.getenv('XDG_CACHE_HOME')
99 path = os.getenv('XDG_CACHE_HOME')
100 if path:
100 if path:
101 path = os.path.join(path, longname, hash)
101 path = os.path.join(path, longname, hash)
102 else:
102 else:
103 path = os.path.join(os.getenv('HOME'), '.cache', longname, hash)
103 path = os.path.join(os.getenv('HOME'), '.cache', longname, hash)
104 else:
104 else:
105 raise util.Abort(_('unknown operating system: %s\n') % os.name)
105 raise util.Abort(_('unknown operating system: %s\n') % os.name)
106 return path
106 return path
107
107
108 def inusercache(ui, hash):
108 def inusercache(ui, hash):
109 return os.path.exists(usercachepath(ui, hash))
109 return os.path.exists(usercachepath(ui, hash))
110
110
111 def findfile(repo, hash):
111 def findfile(repo, hash):
112 if instore(repo, hash):
112 if instore(repo, hash):
113 repo.ui.note(_('Found %s in store\n') % hash)
113 repo.ui.note(_('Found %s in store\n') % hash)
114 elif inusercache(repo.ui, hash):
114 elif inusercache(repo.ui, hash):
115 repo.ui.note(_('Found %s in system cache\n') % hash)
115 repo.ui.note(_('Found %s in system cache\n') % hash)
116 path = storepath(repo, hash)
116 path = storepath(repo, hash)
117 util.makedirs(os.path.dirname(path))
117 util.makedirs(os.path.dirname(path))
118 link(usercachepath(repo.ui, hash), path)
118 link(usercachepath(repo.ui, hash), path)
119 else:
119 else:
120 return None
120 return None
121 return storepath(repo, hash)
121 return storepath(repo, hash)
122
122
123 class largefiles_dirstate(dirstate.dirstate):
123 class largefiles_dirstate(dirstate.dirstate):
124 def __getitem__(self, key):
124 def __getitem__(self, key):
125 return super(largefiles_dirstate, self).__getitem__(unixpath(key))
125 return super(largefiles_dirstate, self).__getitem__(unixpath(key))
126 def normal(self, f):
126 def normal(self, f):
127 return super(largefiles_dirstate, self).normal(unixpath(f))
127 return super(largefiles_dirstate, self).normal(unixpath(f))
128 def remove(self, f):
128 def remove(self, f):
129 return super(largefiles_dirstate, self).remove(unixpath(f))
129 return super(largefiles_dirstate, self).remove(unixpath(f))
130 def add(self, f):
130 def add(self, f):
131 return super(largefiles_dirstate, self).add(unixpath(f))
131 return super(largefiles_dirstate, self).add(unixpath(f))
132 def drop(self, f):
132 def drop(self, f):
133 return super(largefiles_dirstate, self).drop(unixpath(f))
133 return super(largefiles_dirstate, self).drop(unixpath(f))
134 def forget(self, f):
134 def forget(self, f):
135 return super(largefiles_dirstate, self).forget(unixpath(f))
135 return super(largefiles_dirstate, self).forget(unixpath(f))
136
136
137 def openlfdirstate(ui, repo):
137 def openlfdirstate(ui, repo):
138 '''
138 '''
139 Return a dirstate object that tracks largefiles: i.e. its root is
139 Return a dirstate object that tracks largefiles: i.e. its root is
140 the repo root, but it is saved in .hg/largefiles/dirstate.
140 the repo root, but it is saved in .hg/largefiles/dirstate.
141 '''
141 '''
142 admin = repo.join(longname)
142 admin = repo.join(longname)
143 opener = scmutil.opener(admin)
143 opener = scmutil.opener(admin)
144 lfdirstate = largefiles_dirstate(opener, ui, repo.root,
144 lfdirstate = largefiles_dirstate(opener, ui, repo.root,
145 repo.dirstate._validate)
145 repo.dirstate._validate)
146
146
147 # If the largefiles dirstate does not exist, populate and create
147 # If the largefiles dirstate does not exist, populate and create
148 # it. This ensures that we create it on the first meaningful
148 # it. This ensures that we create it on the first meaningful
149 # largefiles operation in a new clone. It also gives us an easy
149 # largefiles operation in a new clone. It also gives us an easy
150 # way to forcibly rebuild largefiles state:
150 # way to forcibly rebuild largefiles state:
151 # rm .hg/largefiles/dirstate && hg status
151 # rm .hg/largefiles/dirstate && hg status
152 # Or even, if things are really messed up:
152 # Or even, if things are really messed up:
153 # rm -rf .hg/largefiles && hg status
153 # rm -rf .hg/largefiles && hg status
154 if not os.path.exists(os.path.join(admin, 'dirstate')):
154 if not os.path.exists(os.path.join(admin, 'dirstate')):
155 util.makedirs(admin)
155 util.makedirs(admin)
156 matcher = getstandinmatcher(repo)
156 matcher = getstandinmatcher(repo)
157 for standin in dirstate_walk(repo.dirstate, matcher):
157 for standin in dirstate_walk(repo.dirstate, matcher):
158 lfile = splitstandin(standin)
158 lfile = splitstandin(standin)
159 hash = readstandin(repo, lfile)
159 hash = readstandin(repo, lfile)
160 lfdirstate.normallookup(lfile)
160 lfdirstate.normallookup(lfile)
161 try:
161 try:
162 if hash == hashfile(repo.wjoin(lfile)):
162 if hash == hashfile(repo.wjoin(lfile)):
163 lfdirstate.normal(lfile)
163 lfdirstate.normal(lfile)
164 except OSError, err:
164 except OSError, err:
165 if err.errno != errno.ENOENT:
165 if err.errno != errno.ENOENT:
166 raise
166 raise
167
167
168 lfdirstate.write()
168 lfdirstate.write()
169
169
170 return lfdirstate
170 return lfdirstate
171
171
172 def lfdirstate_status(lfdirstate, repo, rev):
172 def lfdirstate_status(lfdirstate, repo, rev):
173 wlock = repo.wlock()
173 wlock = repo.wlock()
174 try:
174 try:
175 match = match_.always(repo.root, repo.getcwd())
175 match = match_.always(repo.root, repo.getcwd())
176 s = lfdirstate.status(match, [], False, False, False)
176 s = lfdirstate.status(match, [], False, False, False)
177 unsure, modified, added, removed, missing, unknown, ignored, clean = s
177 unsure, modified, added, removed, missing, unknown, ignored, clean = s
178 for lfile in unsure:
178 for lfile in unsure:
179 if repo[rev][standin(lfile)].data().strip() != \
179 if repo[rev][standin(lfile)].data().strip() != \
180 hashfile(repo.wjoin(lfile)):
180 hashfile(repo.wjoin(lfile)):
181 modified.append(lfile)
181 modified.append(lfile)
182 else:
182 else:
183 clean.append(lfile)
183 clean.append(lfile)
184 lfdirstate.normal(lfile)
184 lfdirstate.normal(lfile)
185 lfdirstate.write()
185 lfdirstate.write()
186 finally:
186 finally:
187 wlock.release()
187 wlock.release()
188 return (modified, added, removed, missing, unknown, ignored, clean)
188 return (modified, added, removed, missing, unknown, ignored, clean)
189
189
190 def listlfiles(repo, rev=None, matcher=None):
190 def listlfiles(repo, rev=None, matcher=None):
191 '''return a list of largefiles in the working copy or the
191 '''return a list of largefiles in the working copy or the
192 specified changeset'''
192 specified changeset'''
193
193
194 if matcher is None:
194 if matcher is None:
195 matcher = getstandinmatcher(repo)
195 matcher = getstandinmatcher(repo)
196
196
197 # ignore unknown files in working directory
197 # ignore unknown files in working directory
198 return [splitstandin(f)
198 return [splitstandin(f)
199 for f in repo[rev].walk(matcher)
199 for f in repo[rev].walk(matcher)
200 if rev is not None or repo.dirstate[f] != '?']
200 if rev is not None or repo.dirstate[f] != '?']
201
201
202 def instore(repo, hash):
202 def instore(repo, hash):
203 return os.path.exists(storepath(repo, hash))
203 return os.path.exists(storepath(repo, hash))
204
204
205 def storepath(repo, hash):
205 def storepath(repo, hash):
206 return repo.join(os.path.join(longname, hash))
206 return repo.join(os.path.join(longname, hash))
207
207
208 def copyfromcache(repo, hash, filename):
208 def copyfromcache(repo, hash, filename):
209 '''Copy the specified largefile from the repo or system cache to
209 '''Copy the specified largefile from the repo or system cache to
210 filename in the repository. Return true on success or false if the
210 filename in the repository. Return true on success or false if the
211 file was not found in either cache (which should not happened:
211 file was not found in either cache (which should not happened:
212 this is meant to be called only after ensuring that the needed
212 this is meant to be called only after ensuring that the needed
213 largefile exists in the cache).'''
213 largefile exists in the cache).'''
214 path = findfile(repo, hash)
214 path = findfile(repo, hash)
215 if path is None:
215 if path is None:
216 return False
216 return False
217 util.makedirs(os.path.dirname(repo.wjoin(filename)))
217 util.makedirs(os.path.dirname(repo.wjoin(filename)))
218 # The write may fail before the file is fully written, but we
218 # The write may fail before the file is fully written, but we
219 # don't use atomic writes in the working copy.
219 # don't use atomic writes in the working copy.
220 shutil.copy(path, repo.wjoin(filename))
220 shutil.copy(path, repo.wjoin(filename))
221 return True
221 return True
222
222
223 def copytostore(repo, rev, file, uploaded=False):
223 def copytostore(repo, rev, file, uploaded=False):
224 hash = readstandin(repo, file)
224 hash = readstandin(repo, file)
225 if instore(repo, hash):
225 if instore(repo, hash):
226 return
226 return
227 copytostoreabsolute(repo, repo.wjoin(file), hash)
227 copytostoreabsolute(repo, repo.wjoin(file), hash)
228
228
229 def copytostoreabsolute(repo, file, hash):
229 def copytostoreabsolute(repo, file, hash):
230 util.makedirs(os.path.dirname(storepath(repo, hash)))
230 util.makedirs(os.path.dirname(storepath(repo, hash)))
231 if inusercache(repo.ui, hash):
231 if inusercache(repo.ui, hash):
232 link(usercachepath(repo.ui, hash), storepath(repo, hash))
232 link(usercachepath(repo.ui, hash), storepath(repo, hash))
233 else:
233 else:
234 dst = util.atomictempfile(storepath(repo, hash))
234 dst = util.atomictempfile(storepath(repo, hash))
235 for chunk in util.filechunkiter(open(file)):
235 for chunk in util.filechunkiter(open(file)):
236 dst.write(chunk)
236 dst.write(chunk)
237 dst.close()
237 dst.close()
238 util.copymode(file, storepath(repo, hash))
238 util.copymode(file, storepath(repo, hash))
239 linktousercache(repo, hash)
239 linktousercache(repo, hash)
240
240
241 def linktousercache(repo, hash):
241 def linktousercache(repo, hash):
242 util.makedirs(os.path.dirname(usercachepath(repo.ui, hash)))
242 util.makedirs(os.path.dirname(usercachepath(repo.ui, hash)))
243 link(storepath(repo, hash), usercachepath(repo.ui, hash))
243 link(storepath(repo, hash), usercachepath(repo.ui, hash))
244
244
245 def getstandinmatcher(repo, pats=[], opts={}):
245 def getstandinmatcher(repo, pats=[], opts={}):
246 '''Return a match object that applies pats to the standin directory'''
246 '''Return a match object that applies pats to the standin directory'''
247 standindir = repo.pathto(shortname)
247 standindir = repo.pathto(shortname)
248 if pats:
248 if pats:
249 # patterns supplied: search standin directory relative to current dir
249 # patterns supplied: search standin directory relative to current dir
250 cwd = repo.getcwd()
250 cwd = repo.getcwd()
251 if os.path.isabs(cwd):
251 if os.path.isabs(cwd):
252 # cwd is an absolute path for hg -R <reponame>
252 # cwd is an absolute path for hg -R <reponame>
253 # work relative to the repository root in this case
253 # work relative to the repository root in this case
254 cwd = ''
254 cwd = ''
255 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
255 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
256 elif os.path.isdir(standindir):
256 elif os.path.isdir(standindir):
257 # no patterns: relative to repo root
257 # no patterns: relative to repo root
258 pats = [standindir]
258 pats = [standindir]
259 else:
259 else:
260 # no patterns and no standin dir: return matcher that matches nothing
260 # no patterns and no standin dir: return matcher that matches nothing
261 match = match_.match(repo.root, None, [], exact=True)
261 match = match_.match(repo.root, None, [], exact=True)
262 match.matchfn = lambda f: False
262 match.matchfn = lambda f: False
263 return match
263 return match
264 return getmatcher(repo, pats, opts, showbad=False)
264 return getmatcher(repo, pats, opts, showbad=False)
265
265
266 def getmatcher(repo, pats=[], opts={}, showbad=True):
266 def getmatcher(repo, pats=[], opts={}, showbad=True):
267 '''Wrapper around scmutil.match() that adds showbad: if false,
267 '''Wrapper around scmutil.match() that adds showbad: if false,
268 neuter the match object's bad() method so it does not print any
268 neuter the match object's bad() method so it does not print any
269 warnings about missing files or directories.'''
269 warnings about missing files or directories.'''
270 match = scmutil.match(repo[None], pats, opts)
270 match = scmutil.match(repo[None], pats, opts)
271
271
272 if not showbad:
272 if not showbad:
273 match.bad = lambda f, msg: None
273 match.bad = lambda f, msg: None
274 return match
274 return match
275
275
276 def composestandinmatcher(repo, rmatcher):
276 def composestandinmatcher(repo, rmatcher):
277 '''Return a matcher that accepts standins corresponding to the
277 '''Return a matcher that accepts standins corresponding to the
278 files accepted by rmatcher. Pass the list of files in the matcher
278 files accepted by rmatcher. Pass the list of files in the matcher
279 as the paths specified by the user.'''
279 as the paths specified by the user.'''
280 smatcher = getstandinmatcher(repo, rmatcher.files())
280 smatcher = getstandinmatcher(repo, rmatcher.files())
281 isstandin = smatcher.matchfn
281 isstandin = smatcher.matchfn
282 def composed_matchfn(f):
282 def composed_matchfn(f):
283 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
283 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
284 smatcher.matchfn = composed_matchfn
284 smatcher.matchfn = composed_matchfn
285
285
286 return smatcher
286 return smatcher
287
287
288 def standin(filename):
288 def standin(filename):
289 '''Return the repo-relative path to the standin for the specified big
289 '''Return the repo-relative path to the standin for the specified big
290 file.'''
290 file.'''
291 # Notes:
291 # Notes:
292 # 1) Most callers want an absolute path, but _create_standin() needs
292 # 1) Most callers want an absolute path, but _create_standin() needs
293 # it repo-relative so lfadd() can pass it to repo_add(). So leave
293 # it repo-relative so lfadd() can pass it to repo_add(). So leave
294 # it up to the caller to use repo.wjoin() to get an absolute path.
294 # it up to the caller to use repo.wjoin() to get an absolute path.
295 # 2) Join with '/' because that's what dirstate always uses, even on
295 # 2) Join with '/' because that's what dirstate always uses, even on
296 # Windows. Change existing separator to '/' first in case we are
296 # Windows. Change existing separator to '/' first in case we are
297 # passed filenames from an external source (like the command line).
297 # passed filenames from an external source (like the command line).
298 return shortname + '/' + filename.replace(os.sep, '/')
298 return shortname + '/' + filename.replace(os.sep, '/')
299
299
300 def isstandin(filename):
300 def isstandin(filename):
301 '''Return true if filename is a big file standin. filename must be
301 '''Return true if filename is a big file standin. filename must be
302 in Mercurial's internal form (slash-separated).'''
302 in Mercurial's internal form (slash-separated).'''
303 return filename.startswith(shortname + '/')
303 return filename.startswith(shortname + '/')
304
304
305 def splitstandin(filename):
305 def splitstandin(filename):
306 # Split on / because that's what dirstate always uses, even on Windows.
306 # Split on / because that's what dirstate always uses, even on Windows.
307 # Change local separator to / first just in case we are passed filenames
307 # Change local separator to / first just in case we are passed filenames
308 # from an external source (like the command line).
308 # from an external source (like the command line).
309 bits = filename.replace(os.sep, '/').split('/', 1)
309 bits = filename.replace(os.sep, '/').split('/', 1)
310 if len(bits) == 2 and bits[0] == shortname:
310 if len(bits) == 2 and bits[0] == shortname:
311 return bits[1]
311 return bits[1]
312 else:
312 else:
313 return None
313 return None
314
314
315 def updatestandin(repo, standin):
315 def updatestandin(repo, standin):
316 file = repo.wjoin(splitstandin(standin))
316 file = repo.wjoin(splitstandin(standin))
317 if os.path.exists(file):
317 if os.path.exists(file):
318 hash = hashfile(file)
318 hash = hashfile(file)
319 executable = getexecutable(file)
319 executable = getexecutable(file)
320 writestandin(repo, standin, hash, executable)
320 writestandin(repo, standin, hash, executable)
321
321
322 def readstandin(repo, filename, node=None):
322 def readstandin(repo, filename, node=None):
323 '''read hex hash from standin for filename at given node, or working
323 '''read hex hash from standin for filename at given node, or working
324 directory if no node is given'''
324 directory if no node is given'''
325 return repo[node][standin(filename)].data().strip()
325 return repo[node][standin(filename)].data().strip()
326
326
327 def writestandin(repo, standin, hash, executable):
327 def writestandin(repo, standin, hash, executable):
328 '''write hash to <repo.root>/<standin>'''
328 '''write hash to <repo.root>/<standin>'''
329 writehash(hash, repo.wjoin(standin), executable)
329 writehash(hash, repo.wjoin(standin), executable)
330
330
331 def copyandhash(instream, outfile):
331 def copyandhash(instream, outfile):
332 '''Read bytes from instream (iterable) and write them to outfile,
332 '''Read bytes from instream (iterable) and write them to outfile,
333 computing the SHA-1 hash of the data along the way. Close outfile
333 computing the SHA-1 hash of the data along the way. Close outfile
334 when done and return the binary hash.'''
334 when done and return the binary hash.'''
335 hasher = util.sha1('')
335 hasher = util.sha1('')
336 for data in instream:
336 for data in instream:
337 hasher.update(data)
337 hasher.update(data)
338 outfile.write(data)
338 outfile.write(data)
339
339
340 # Blecch: closing a file that somebody else opened is rude and
340 # Blecch: closing a file that somebody else opened is rude and
341 # wrong. But it's so darn convenient and practical! After all,
341 # wrong. But it's so darn convenient and practical! After all,
342 # outfile was opened just to copy and hash.
342 # outfile was opened just to copy and hash.
343 outfile.close()
343 outfile.close()
344
344
345 return hasher.digest()
345 return hasher.digest()
346
346
347 def hashrepofile(repo, file):
347 def hashrepofile(repo, file):
348 return hashfile(repo.wjoin(file))
348 return hashfile(repo.wjoin(file))
349
349
350 def hashfile(file):
350 def hashfile(file):
351 if not os.path.exists(file):
351 if not os.path.exists(file):
352 return ''
352 return ''
353 hasher = util.sha1('')
353 hasher = util.sha1('')
354 fd = open(file, 'rb')
354 fd = open(file, 'rb')
355 for data in blockstream(fd):
355 for data in blockstream(fd):
356 hasher.update(data)
356 hasher.update(data)
357 fd.close()
357 fd.close()
358 return hasher.hexdigest()
358 return hasher.hexdigest()
359
359
360 class limitreader(object):
360 class limitreader(object):
361 def __init__(self, f, limit):
361 def __init__(self, f, limit):
362 self.f = f
362 self.f = f
363 self.limit = limit
363 self.limit = limit
364
364
365 def read(self, length):
365 def read(self, length):
366 if self.limit == 0:
366 if self.limit == 0:
367 return ''
367 return ''
368 length = length > self.limit and self.limit or length
368 length = length > self.limit and self.limit or length
369 self.limit -= length
369 self.limit -= length
370 return self.f.read(length)
370 return self.f.read(length)
371
371
372 def close(self):
372 def close(self):
373 pass
373 pass
374
374
375 def blockstream(infile, blocksize=128 * 1024):
375 def blockstream(infile, blocksize=128 * 1024):
376 """Generator that yields blocks of data from infile and closes infile."""
376 """Generator that yields blocks of data from infile and closes infile."""
377 while True:
377 while True:
378 data = infile.read(blocksize)
378 data = infile.read(blocksize)
379 if not data:
379 if not data:
380 break
380 break
381 yield data
381 yield data
382 # same blecch as copyandhash() above
382 # same blecch as copyandhash() above
383 infile.close()
383 infile.close()
384
384
385 def readhash(filename):
385 def readhash(filename):
386 rfile = open(filename, 'rb')
386 rfile = open(filename, 'rb')
387 hash = rfile.read(40)
387 hash = rfile.read(40)
388 rfile.close()
388 rfile.close()
389 if len(hash) < 40:
389 if len(hash) < 40:
390 raise util.Abort(_('bad hash in \'%s\' (only %d bytes long)')
390 raise util.Abort(_('bad hash in \'%s\' (only %d bytes long)')
391 % (filename, len(hash)))
391 % (filename, len(hash)))
392 return hash
392 return hash
393
393
394 def writehash(hash, filename, executable):
394 def writehash(hash, filename, executable):
395 util.makedirs(os.path.dirname(filename))
395 util.makedirs(os.path.dirname(filename))
396 if os.path.exists(filename):
396 util.writefile(filename, hash + '\n')
397 os.unlink(filename)
397 os.chmod(filename, getmode(executable))
398 wfile = open(filename, 'wb')
399
400 try:
401 wfile.write(hash)
402 wfile.write('\n')
403 finally:
404 wfile.close()
405 if os.path.exists(filename):
406 os.chmod(filename, getmode(executable))
407
398
408 def getexecutable(filename):
399 def getexecutable(filename):
409 mode = os.stat(filename).st_mode
400 mode = os.stat(filename).st_mode
410 return ((mode & stat.S_IXUSR) and
401 return ((mode & stat.S_IXUSR) and
411 (mode & stat.S_IXGRP) and
402 (mode & stat.S_IXGRP) and
412 (mode & stat.S_IXOTH))
403 (mode & stat.S_IXOTH))
413
404
414 def getmode(executable):
405 def getmode(executable):
415 if executable:
406 if executable:
416 return 0755
407 return 0755
417 else:
408 else:
418 return 0644
409 return 0644
419
410
420 def urljoin(first, second, *arg):
411 def urljoin(first, second, *arg):
421 def join(left, right):
412 def join(left, right):
422 if not left.endswith('/'):
413 if not left.endswith('/'):
423 left += '/'
414 left += '/'
424 if right.startswith('/'):
415 if right.startswith('/'):
425 right = right[1:]
416 right = right[1:]
426 return left + right
417 return left + right
427
418
428 url = join(first, second)
419 url = join(first, second)
429 for a in arg:
420 for a in arg:
430 url = join(url, a)
421 url = join(url, a)
431 return url
422 return url
432
423
433 def hexsha1(data):
424 def hexsha1(data):
434 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
425 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
435 object data"""
426 object data"""
436 h = util.sha1()
427 h = util.sha1()
437 for chunk in util.filechunkiter(data):
428 for chunk in util.filechunkiter(data):
438 h.update(chunk)
429 h.update(chunk)
439 return h.hexdigest()
430 return h.hexdigest()
440
431
441 def httpsendfile(ui, filename):
432 def httpsendfile(ui, filename):
442 return httpconnection.httpsendfile(ui, filename, 'rb')
433 return httpconnection.httpsendfile(ui, filename, 'rb')
443
434
444 def unixpath(path):
435 def unixpath(path):
445 '''Return a version of path normalized for use with the lfdirstate.'''
436 '''Return a version of path normalized for use with the lfdirstate.'''
446 return os.path.normpath(path).replace(os.sep, '/')
437 return os.path.normpath(path).replace(os.sep, '/')
447
438
448 def islfilesrepo(repo):
439 def islfilesrepo(repo):
449 return ('largefiles' in repo.requirements and
440 return ('largefiles' in repo.requirements and
450 util.any(shortname + '/' in f[0] for f in repo.store.datafiles()))
441 util.any(shortname + '/' in f[0] for f in repo.store.datafiles()))
451
442
452 def mkstemp(repo, prefix):
443 def mkstemp(repo, prefix):
453 '''Returns a file descriptor and a filename corresponding to a temporary
444 '''Returns a file descriptor and a filename corresponding to a temporary
454 file in the repo's largefiles store.'''
445 file in the repo's largefiles store.'''
455 path = repo.join(longname)
446 path = repo.join(longname)
456 util.makedirs(path)
447 util.makedirs(path)
457 return tempfile.mkstemp(prefix=prefix, dir=path)
448 return tempfile.mkstemp(prefix=prefix, dir=path)
458
449
459 class storeprotonotcapable(Exception):
450 class storeprotonotcapable(Exception):
460 def __init__(self, storetypes):
451 def __init__(self, storetypes):
461 self.storetypes = storetypes
452 self.storetypes = storetypes
General Comments 0
You need to be logged in to leave comments. Login now