##// END OF EJS Templates
largefiles: write .hg/largefiles/ files atomically...
Martin Geisler -
r15571:80978811 stable
parent child Browse files
Show More
@@ -0,0 +1,39 b''
1 Test how largefiles abort in case the disk runs full
2
3 $ cat > criple.py <<EOF
4 > import os, errno, shutil
5 > from mercurial import util
6 > #
7 > # this makes the original largefiles code abort:
8 > def copyfileobj(fsrc, fdst, length=16*1024):
9 > fdst.write(fsrc.read(4))
10 > raise IOError(errno.ENOSPC, os.strerror(errno.ENOSPC))
11 > shutil.copyfileobj = copyfileobj
12 > #
13 > # this makes the rewritten code abort:
14 > def filechunkiter(f, size=65536, limit=None):
15 > yield f.read(4)
16 > raise IOError(errno.ENOSPC, os.strerror(errno.ENOSPC))
17 > util.filechunkiter = filechunkiter
18 > EOF
19
20 $ echo "[extensions]" >> $HGRCPATH
21 $ echo "largefiles =" >> $HGRCPATH
22
23 $ hg init alice
24 $ cd alice
25 $ echo "this is a very big file" > big
26 $ hg add --large big
27 $ hg commit --config extensions.criple=$TESTTMP/criple.py -m big
28 abort: No space left on device
29 [255]
30
31 The largefile is not created in .hg/largefiles:
32
33 $ ls .hg/largefiles
34 dirstate
35
36 The user cache is not even created:
37
38 >>> import os; os.path.exists("$HOME/.cache/largefiles/")
39 False
@@ -1,455 +1,458 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 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
215 # The write may fail before the file is fully written, but we
216 # don't use atomic writes in the working copy.
216 # don't use atomic writes in the working copy.
217 shutil.copy(path, repo.wjoin(filename))
217 shutil.copy(path, repo.wjoin(filename))
218 return True
218 return True
219
219
220 def copytostore(repo, rev, file, uploaded=False):
220 def copytostore(repo, rev, file, uploaded=False):
221 hash = readstandin(repo, file)
221 hash = readstandin(repo, file)
222 if instore(repo, hash):
222 if instore(repo, hash):
223 return
223 return
224 copytostoreabsolute(repo, repo.wjoin(file), hash)
224 copytostoreabsolute(repo, repo.wjoin(file), hash)
225
225
226 def copytostoreabsolute(repo, file, hash):
226 def copytostoreabsolute(repo, file, hash):
227 util.makedirs(os.path.dirname(storepath(repo, hash)))
227 util.makedirs(os.path.dirname(storepath(repo, hash)))
228 if inusercache(repo.ui, hash):
228 if inusercache(repo.ui, hash):
229 link(usercachepath(repo.ui, hash), storepath(repo, hash))
229 link(usercachepath(repo.ui, hash), storepath(repo, hash))
230 else:
230 else:
231 shutil.copyfile(file, storepath(repo, hash))
231 dst = util.atomictempfile(storepath(repo, hash))
232 os.chmod(storepath(repo, hash), os.stat(file).st_mode)
232 for chunk in util.filechunkiter(open(file)):
233 dst.write(chunk)
234 dst.close()
235 util.copymode(file, storepath(repo, hash))
233 linktousercache(repo, hash)
236 linktousercache(repo, hash)
234
237
235 def linktousercache(repo, hash):
238 def linktousercache(repo, hash):
236 util.makedirs(os.path.dirname(usercachepath(repo.ui, hash)))
239 util.makedirs(os.path.dirname(usercachepath(repo.ui, hash)))
237 link(storepath(repo, hash), usercachepath(repo.ui, hash))
240 link(storepath(repo, hash), usercachepath(repo.ui, hash))
238
241
239 def getstandinmatcher(repo, pats=[], opts={}):
242 def getstandinmatcher(repo, pats=[], opts={}):
240 '''Return a match object that applies pats to the standin directory'''
243 '''Return a match object that applies pats to the standin directory'''
241 standindir = repo.pathto(shortname)
244 standindir = repo.pathto(shortname)
242 if pats:
245 if pats:
243 # patterns supplied: search standin directory relative to current dir
246 # patterns supplied: search standin directory relative to current dir
244 cwd = repo.getcwd()
247 cwd = repo.getcwd()
245 if os.path.isabs(cwd):
248 if os.path.isabs(cwd):
246 # cwd is an absolute path for hg -R <reponame>
249 # cwd is an absolute path for hg -R <reponame>
247 # work relative to the repository root in this case
250 # work relative to the repository root in this case
248 cwd = ''
251 cwd = ''
249 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
252 pats = [os.path.join(standindir, cwd, pat) for pat in pats]
250 elif os.path.isdir(standindir):
253 elif os.path.isdir(standindir):
251 # no patterns: relative to repo root
254 # no patterns: relative to repo root
252 pats = [standindir]
255 pats = [standindir]
253 else:
256 else:
254 # no patterns and no standin dir: return matcher that matches nothing
257 # no patterns and no standin dir: return matcher that matches nothing
255 match = match_.match(repo.root, None, [], exact=True)
258 match = match_.match(repo.root, None, [], exact=True)
256 match.matchfn = lambda f: False
259 match.matchfn = lambda f: False
257 return match
260 return match
258 return getmatcher(repo, pats, opts, showbad=False)
261 return getmatcher(repo, pats, opts, showbad=False)
259
262
260 def getmatcher(repo, pats=[], opts={}, showbad=True):
263 def getmatcher(repo, pats=[], opts={}, showbad=True):
261 '''Wrapper around scmutil.match() that adds showbad: if false,
264 '''Wrapper around scmutil.match() that adds showbad: if false,
262 neuter the match object's bad() method so it does not print any
265 neuter the match object's bad() method so it does not print any
263 warnings about missing files or directories.'''
266 warnings about missing files or directories.'''
264 match = scmutil.match(repo[None], pats, opts)
267 match = scmutil.match(repo[None], pats, opts)
265
268
266 if not showbad:
269 if not showbad:
267 match.bad = lambda f, msg: None
270 match.bad = lambda f, msg: None
268 return match
271 return match
269
272
270 def composestandinmatcher(repo, rmatcher):
273 def composestandinmatcher(repo, rmatcher):
271 '''Return a matcher that accepts standins corresponding to the
274 '''Return a matcher that accepts standins corresponding to the
272 files accepted by rmatcher. Pass the list of files in the matcher
275 files accepted by rmatcher. Pass the list of files in the matcher
273 as the paths specified by the user.'''
276 as the paths specified by the user.'''
274 smatcher = getstandinmatcher(repo, rmatcher.files())
277 smatcher = getstandinmatcher(repo, rmatcher.files())
275 isstandin = smatcher.matchfn
278 isstandin = smatcher.matchfn
276 def composed_matchfn(f):
279 def composed_matchfn(f):
277 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
280 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
278 smatcher.matchfn = composed_matchfn
281 smatcher.matchfn = composed_matchfn
279
282
280 return smatcher
283 return smatcher
281
284
282 def standin(filename):
285 def standin(filename):
283 '''Return the repo-relative path to the standin for the specified big
286 '''Return the repo-relative path to the standin for the specified big
284 file.'''
287 file.'''
285 # Notes:
288 # Notes:
286 # 1) Most callers want an absolute path, but _create_standin() needs
289 # 1) Most callers want an absolute path, but _create_standin() needs
287 # it repo-relative so lfadd() can pass it to repo_add(). So leave
290 # it repo-relative so lfadd() can pass it to repo_add(). So leave
288 # it up to the caller to use repo.wjoin() to get an absolute path.
291 # it up to the caller to use repo.wjoin() to get an absolute path.
289 # 2) Join with '/' because that's what dirstate always uses, even on
292 # 2) Join with '/' because that's what dirstate always uses, even on
290 # Windows. Change existing separator to '/' first in case we are
293 # Windows. Change existing separator to '/' first in case we are
291 # passed filenames from an external source (like the command line).
294 # passed filenames from an external source (like the command line).
292 return shortname + '/' + filename.replace(os.sep, '/')
295 return shortname + '/' + filename.replace(os.sep, '/')
293
296
294 def isstandin(filename):
297 def isstandin(filename):
295 '''Return true if filename is a big file standin. filename must be
298 '''Return true if filename is a big file standin. filename must be
296 in Mercurial's internal form (slash-separated).'''
299 in Mercurial's internal form (slash-separated).'''
297 return filename.startswith(shortname + '/')
300 return filename.startswith(shortname + '/')
298
301
299 def splitstandin(filename):
302 def splitstandin(filename):
300 # Split on / because that's what dirstate always uses, even on Windows.
303 # Split on / because that's what dirstate always uses, even on Windows.
301 # Change local separator to / first just in case we are passed filenames
304 # Change local separator to / first just in case we are passed filenames
302 # from an external source (like the command line).
305 # from an external source (like the command line).
303 bits = filename.replace(os.sep, '/').split('/', 1)
306 bits = filename.replace(os.sep, '/').split('/', 1)
304 if len(bits) == 2 and bits[0] == shortname:
307 if len(bits) == 2 and bits[0] == shortname:
305 return bits[1]
308 return bits[1]
306 else:
309 else:
307 return None
310 return None
308
311
309 def updatestandin(repo, standin):
312 def updatestandin(repo, standin):
310 file = repo.wjoin(splitstandin(standin))
313 file = repo.wjoin(splitstandin(standin))
311 if os.path.exists(file):
314 if os.path.exists(file):
312 hash = hashfile(file)
315 hash = hashfile(file)
313 executable = getexecutable(file)
316 executable = getexecutable(file)
314 writestandin(repo, standin, hash, executable)
317 writestandin(repo, standin, hash, executable)
315
318
316 def readstandin(repo, filename, node=None):
319 def readstandin(repo, filename, node=None):
317 '''read hex hash from standin for filename at given node, or working
320 '''read hex hash from standin for filename at given node, or working
318 directory if no node is given'''
321 directory if no node is given'''
319 return repo[node][standin(filename)].data().strip()
322 return repo[node][standin(filename)].data().strip()
320
323
321 def writestandin(repo, standin, hash, executable):
324 def writestandin(repo, standin, hash, executable):
322 '''write hash to <repo.root>/<standin>'''
325 '''write hash to <repo.root>/<standin>'''
323 writehash(hash, repo.wjoin(standin), executable)
326 writehash(hash, repo.wjoin(standin), executable)
324
327
325 def copyandhash(instream, outfile):
328 def copyandhash(instream, outfile):
326 '''Read bytes from instream (iterable) and write them to outfile,
329 '''Read bytes from instream (iterable) and write them to outfile,
327 computing the SHA-1 hash of the data along the way. Close outfile
330 computing the SHA-1 hash of the data along the way. Close outfile
328 when done and return the binary hash.'''
331 when done and return the binary hash.'''
329 hasher = util.sha1('')
332 hasher = util.sha1('')
330 for data in instream:
333 for data in instream:
331 hasher.update(data)
334 hasher.update(data)
332 outfile.write(data)
335 outfile.write(data)
333
336
334 # Blecch: closing a file that somebody else opened is rude and
337 # Blecch: closing a file that somebody else opened is rude and
335 # wrong. But it's so darn convenient and practical! After all,
338 # wrong. But it's so darn convenient and practical! After all,
336 # outfile was opened just to copy and hash.
339 # outfile was opened just to copy and hash.
337 outfile.close()
340 outfile.close()
338
341
339 return hasher.digest()
342 return hasher.digest()
340
343
341 def hashrepofile(repo, file):
344 def hashrepofile(repo, file):
342 return hashfile(repo.wjoin(file))
345 return hashfile(repo.wjoin(file))
343
346
344 def hashfile(file):
347 def hashfile(file):
345 if not os.path.exists(file):
348 if not os.path.exists(file):
346 return ''
349 return ''
347 hasher = util.sha1('')
350 hasher = util.sha1('')
348 fd = open(file, 'rb')
351 fd = open(file, 'rb')
349 for data in blockstream(fd):
352 for data in blockstream(fd):
350 hasher.update(data)
353 hasher.update(data)
351 fd.close()
354 fd.close()
352 return hasher.hexdigest()
355 return hasher.hexdigest()
353
356
354 class limitreader(object):
357 class limitreader(object):
355 def __init__(self, f, limit):
358 def __init__(self, f, limit):
356 self.f = f
359 self.f = f
357 self.limit = limit
360 self.limit = limit
358
361
359 def read(self, length):
362 def read(self, length):
360 if self.limit == 0:
363 if self.limit == 0:
361 return ''
364 return ''
362 length = length > self.limit and self.limit or length
365 length = length > self.limit and self.limit or length
363 self.limit -= length
366 self.limit -= length
364 return self.f.read(length)
367 return self.f.read(length)
365
368
366 def close(self):
369 def close(self):
367 pass
370 pass
368
371
369 def blockstream(infile, blocksize=128 * 1024):
372 def blockstream(infile, blocksize=128 * 1024):
370 """Generator that yields blocks of data from infile and closes infile."""
373 """Generator that yields blocks of data from infile and closes infile."""
371 while True:
374 while True:
372 data = infile.read(blocksize)
375 data = infile.read(blocksize)
373 if not data:
376 if not data:
374 break
377 break
375 yield data
378 yield data
376 # same blecch as copyandhash() above
379 # same blecch as copyandhash() above
377 infile.close()
380 infile.close()
378
381
379 def readhash(filename):
382 def readhash(filename):
380 rfile = open(filename, 'rb')
383 rfile = open(filename, 'rb')
381 hash = rfile.read(40)
384 hash = rfile.read(40)
382 rfile.close()
385 rfile.close()
383 if len(hash) < 40:
386 if len(hash) < 40:
384 raise util.Abort(_('bad hash in \'%s\' (only %d bytes long)')
387 raise util.Abort(_('bad hash in \'%s\' (only %d bytes long)')
385 % (filename, len(hash)))
388 % (filename, len(hash)))
386 return hash
389 return hash
387
390
388 def writehash(hash, filename, executable):
391 def writehash(hash, filename, executable):
389 util.makedirs(os.path.dirname(filename))
392 util.makedirs(os.path.dirname(filename))
390 if os.path.exists(filename):
393 if os.path.exists(filename):
391 os.unlink(filename)
394 os.unlink(filename)
392 wfile = open(filename, 'wb')
395 wfile = open(filename, 'wb')
393
396
394 try:
397 try:
395 wfile.write(hash)
398 wfile.write(hash)
396 wfile.write('\n')
399 wfile.write('\n')
397 finally:
400 finally:
398 wfile.close()
401 wfile.close()
399 if os.path.exists(filename):
402 if os.path.exists(filename):
400 os.chmod(filename, getmode(executable))
403 os.chmod(filename, getmode(executable))
401
404
402 def getexecutable(filename):
405 def getexecutable(filename):
403 mode = os.stat(filename).st_mode
406 mode = os.stat(filename).st_mode
404 return ((mode & stat.S_IXUSR) and
407 return ((mode & stat.S_IXUSR) and
405 (mode & stat.S_IXGRP) and
408 (mode & stat.S_IXGRP) and
406 (mode & stat.S_IXOTH))
409 (mode & stat.S_IXOTH))
407
410
408 def getmode(executable):
411 def getmode(executable):
409 if executable:
412 if executable:
410 return 0755
413 return 0755
411 else:
414 else:
412 return 0644
415 return 0644
413
416
414 def urljoin(first, second, *arg):
417 def urljoin(first, second, *arg):
415 def join(left, right):
418 def join(left, right):
416 if not left.endswith('/'):
419 if not left.endswith('/'):
417 left += '/'
420 left += '/'
418 if right.startswith('/'):
421 if right.startswith('/'):
419 right = right[1:]
422 right = right[1:]
420 return left + right
423 return left + right
421
424
422 url = join(first, second)
425 url = join(first, second)
423 for a in arg:
426 for a in arg:
424 url = join(url, a)
427 url = join(url, a)
425 return url
428 return url
426
429
427 def hexsha1(data):
430 def hexsha1(data):
428 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
431 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
429 object data"""
432 object data"""
430 h = util.sha1()
433 h = util.sha1()
431 for chunk in util.filechunkiter(data):
434 for chunk in util.filechunkiter(data):
432 h.update(chunk)
435 h.update(chunk)
433 return h.hexdigest()
436 return h.hexdigest()
434
437
435 def httpsendfile(ui, filename):
438 def httpsendfile(ui, filename):
436 return httpconnection.httpsendfile(ui, filename, 'rb')
439 return httpconnection.httpsendfile(ui, filename, 'rb')
437
440
438 def unixpath(path):
441 def unixpath(path):
439 '''Return a version of path normalized for use with the lfdirstate.'''
442 '''Return a version of path normalized for use with the lfdirstate.'''
440 return os.path.normpath(path).replace(os.sep, '/')
443 return os.path.normpath(path).replace(os.sep, '/')
441
444
442 def islfilesrepo(repo):
445 def islfilesrepo(repo):
443 return ('largefiles' in repo.requirements and
446 return ('largefiles' in repo.requirements and
444 util.any(shortname + '/' in f[0] for f in repo.store.datafiles()))
447 util.any(shortname + '/' in f[0] for f in repo.store.datafiles()))
445
448
446 def mkstemp(repo, prefix):
449 def mkstemp(repo, prefix):
447 '''Returns a file descriptor and a filename corresponding to a temporary
450 '''Returns a file descriptor and a filename corresponding to a temporary
448 file in the repo's largefiles store.'''
451 file in the repo's largefiles store.'''
449 path = repo.join(longname)
452 path = repo.join(longname)
450 util.makedirs(path)
453 util.makedirs(path)
451 return tempfile.mkstemp(prefix=prefix, dir=path)
454 return tempfile.mkstemp(prefix=prefix, dir=path)
452
455
453 class storeprotonotcapable(Exception):
456 class storeprotonotcapable(Exception):
454 def __init__(self, storetypes):
457 def __init__(self, storetypes):
455 self.storetypes = storetypes
458 self.storetypes = storetypes
General Comments 0
You need to be logged in to leave comments. Login now