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