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