##// END OF EJS Templates
lfutil: avoid creating unnecessary copy of status tuple...
Martin von Zweigbergk -
r22912:3b8e6c09 default
parent child Browse files
Show More
@@ -1,416 +1,416 b''
1 1 # Copyright 2009-2010 Gregory P. Ward
2 2 # Copyright 2009-2010 Intelerad Medical Systems Incorporated
3 3 # Copyright 2010-2011 Fog Creek Software
4 4 # Copyright 2010-2011 Unity Technologies
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 '''largefiles utility code: must not import other modules in this package.'''
10 10
11 11 import os
12 12 import platform
13 13 import shutil
14 14 import stat
15 15
16 16 from mercurial import dirstate, httpconnection, match as match_, util, scmutil
17 17 from mercurial.i18n import _
18 18 from mercurial import node
19 19
20 20 shortname = '.hglf'
21 21 shortnameslash = shortname + '/'
22 22 longname = 'largefiles'
23 23
24 24
25 25 # -- Private worker functions ------------------------------------------
26 26
27 27 def getminsize(ui, assumelfiles, opt, default=10):
28 28 lfsize = opt
29 29 if not lfsize and assumelfiles:
30 30 lfsize = ui.config(longname, 'minsize', default=default)
31 31 if lfsize:
32 32 try:
33 33 lfsize = float(lfsize)
34 34 except ValueError:
35 35 raise util.Abort(_('largefiles: size must be number (not %s)\n')
36 36 % lfsize)
37 37 if lfsize is None:
38 38 raise util.Abort(_('minimum size for largefiles must be specified'))
39 39 return lfsize
40 40
41 41 def link(src, dest):
42 42 util.makedirs(os.path.dirname(dest))
43 43 try:
44 44 util.oslink(src, dest)
45 45 except OSError:
46 46 # if hardlinks fail, fallback on atomic copy
47 47 dst = util.atomictempfile(dest)
48 48 for chunk in util.filechunkiter(open(src, 'rb')):
49 49 dst.write(chunk)
50 50 dst.close()
51 51 os.chmod(dest, os.stat(src).st_mode)
52 52
53 53 def usercachepath(ui, hash):
54 54 path = ui.configpath(longname, 'usercache', None)
55 55 if path:
56 56 path = os.path.join(path, hash)
57 57 else:
58 58 if os.name == 'nt':
59 59 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
60 60 if appdata:
61 61 path = os.path.join(appdata, longname, hash)
62 62 elif platform.system() == 'Darwin':
63 63 home = os.getenv('HOME')
64 64 if home:
65 65 path = os.path.join(home, 'Library', 'Caches',
66 66 longname, hash)
67 67 elif os.name == 'posix':
68 68 path = os.getenv('XDG_CACHE_HOME')
69 69 if path:
70 70 path = os.path.join(path, longname, hash)
71 71 else:
72 72 home = os.getenv('HOME')
73 73 if home:
74 74 path = os.path.join(home, '.cache', longname, hash)
75 75 else:
76 76 raise util.Abort(_('unknown operating system: %s\n') % os.name)
77 77 return path
78 78
79 79 def inusercache(ui, hash):
80 80 path = usercachepath(ui, hash)
81 81 return path and os.path.exists(path)
82 82
83 83 def findfile(repo, hash):
84 84 if instore(repo, hash):
85 85 repo.ui.note(_('found %s in store\n') % hash)
86 86 return storepath(repo, hash)
87 87 elif inusercache(repo.ui, hash):
88 88 repo.ui.note(_('found %s in system cache\n') % hash)
89 89 path = storepath(repo, hash)
90 90 link(usercachepath(repo.ui, hash), path)
91 91 return path
92 92 return None
93 93
94 94 class largefilesdirstate(dirstate.dirstate):
95 95 def __getitem__(self, key):
96 96 return super(largefilesdirstate, self).__getitem__(unixpath(key))
97 97 def normal(self, f):
98 98 return super(largefilesdirstate, self).normal(unixpath(f))
99 99 def remove(self, f):
100 100 return super(largefilesdirstate, self).remove(unixpath(f))
101 101 def add(self, f):
102 102 return super(largefilesdirstate, self).add(unixpath(f))
103 103 def drop(self, f):
104 104 return super(largefilesdirstate, self).drop(unixpath(f))
105 105 def forget(self, f):
106 106 return super(largefilesdirstate, self).forget(unixpath(f))
107 107 def normallookup(self, f):
108 108 return super(largefilesdirstate, self).normallookup(unixpath(f))
109 109 def _ignore(self, f):
110 110 return False
111 111
112 112 def openlfdirstate(ui, repo, create=True):
113 113 '''
114 114 Return a dirstate object that tracks largefiles: i.e. its root is
115 115 the repo root, but it is saved in .hg/largefiles/dirstate.
116 116 '''
117 117 lfstoredir = repo.join(longname)
118 118 opener = scmutil.opener(lfstoredir)
119 119 lfdirstate = largefilesdirstate(opener, ui, repo.root,
120 120 repo.dirstate._validate)
121 121
122 122 # If the largefiles dirstate does not exist, populate and create
123 123 # it. This ensures that we create it on the first meaningful
124 124 # largefiles operation in a new clone.
125 125 if create and not os.path.exists(os.path.join(lfstoredir, 'dirstate')):
126 126 matcher = getstandinmatcher(repo)
127 127 standins = repo.dirstate.walk(matcher, [], False, False)
128 128
129 129 if len(standins) > 0:
130 130 util.makedirs(lfstoredir)
131 131
132 132 for standin in standins:
133 133 lfile = splitstandin(standin)
134 134 lfdirstate.normallookup(lfile)
135 135 return lfdirstate
136 136
137 137 def lfdirstatestatus(lfdirstate, repo, rev):
138 138 match = match_.always(repo.root, repo.getcwd())
139 139 unsure, s = lfdirstate.status(match, [], False, False, False)
140 modified, added, removed, missing, unknown, ignored, clean = s
140 modified, _added, _removed, _missing, _unknown, _ignored, clean = s
141 141 for lfile in unsure:
142 142 try:
143 143 fctx = repo[rev][standin(lfile)]
144 144 except LookupError:
145 145 fctx = None
146 146 if not fctx or fctx.data().strip() != hashfile(repo.wjoin(lfile)):
147 147 modified.append(lfile)
148 148 else:
149 149 clean.append(lfile)
150 150 lfdirstate.normal(lfile)
151 return (modified, added, removed, missing, unknown, ignored, clean)
151 return s
152 152
153 153 def listlfiles(repo, rev=None, matcher=None):
154 154 '''return a list of largefiles in the working copy or the
155 155 specified changeset'''
156 156
157 157 if matcher is None:
158 158 matcher = getstandinmatcher(repo)
159 159
160 160 # ignore unknown files in working directory
161 161 return [splitstandin(f)
162 162 for f in repo[rev].walk(matcher)
163 163 if rev is not None or repo.dirstate[f] != '?']
164 164
165 165 def instore(repo, hash):
166 166 return os.path.exists(storepath(repo, hash))
167 167
168 168 def storepath(repo, hash):
169 169 return repo.join(os.path.join(longname, hash))
170 170
171 171 def copyfromcache(repo, hash, filename):
172 172 '''Copy the specified largefile from the repo or system cache to
173 173 filename in the repository. Return true on success or false if the
174 174 file was not found in either cache (which should not happened:
175 175 this is meant to be called only after ensuring that the needed
176 176 largefile exists in the cache).'''
177 177 path = findfile(repo, hash)
178 178 if path is None:
179 179 return False
180 180 util.makedirs(os.path.dirname(repo.wjoin(filename)))
181 181 # The write may fail before the file is fully written, but we
182 182 # don't use atomic writes in the working copy.
183 183 shutil.copy(path, repo.wjoin(filename))
184 184 return True
185 185
186 186 def copytostore(repo, rev, file, uploaded=False):
187 187 hash = readstandin(repo, file, rev)
188 188 if instore(repo, hash):
189 189 return
190 190 copytostoreabsolute(repo, repo.wjoin(file), hash)
191 191
192 192 def copyalltostore(repo, node):
193 193 '''Copy all largefiles in a given revision to the store'''
194 194
195 195 ctx = repo[node]
196 196 for filename in ctx.files():
197 197 if isstandin(filename) and filename in ctx.manifest():
198 198 realfile = splitstandin(filename)
199 199 copytostore(repo, ctx.node(), realfile)
200 200
201 201
202 202 def copytostoreabsolute(repo, file, hash):
203 203 if inusercache(repo.ui, hash):
204 204 link(usercachepath(repo.ui, hash), storepath(repo, hash))
205 205 elif not getattr(repo, "_isconverting", False):
206 206 util.makedirs(os.path.dirname(storepath(repo, hash)))
207 207 dst = util.atomictempfile(storepath(repo, hash),
208 208 createmode=repo.store.createmode)
209 209 for chunk in util.filechunkiter(open(file, 'rb')):
210 210 dst.write(chunk)
211 211 dst.close()
212 212 linktousercache(repo, hash)
213 213
214 214 def linktousercache(repo, hash):
215 215 path = usercachepath(repo.ui, hash)
216 216 if path:
217 217 link(storepath(repo, hash), path)
218 218
219 219 def getstandinmatcher(repo, pats=[], opts={}):
220 220 '''Return a match object that applies pats to the standin directory'''
221 221 standindir = repo.wjoin(shortname)
222 222 if pats:
223 223 pats = [os.path.join(standindir, pat) for pat in pats]
224 224 else:
225 225 # no patterns: relative to repo root
226 226 pats = [standindir]
227 227 # no warnings about missing files or directories
228 228 match = scmutil.match(repo[None], pats, opts)
229 229 match.bad = lambda f, msg: None
230 230 return match
231 231
232 232 def composestandinmatcher(repo, rmatcher):
233 233 '''Return a matcher that accepts standins corresponding to the
234 234 files accepted by rmatcher. Pass the list of files in the matcher
235 235 as the paths specified by the user.'''
236 236 smatcher = getstandinmatcher(repo, rmatcher.files())
237 237 isstandin = smatcher.matchfn
238 238 def composedmatchfn(f):
239 239 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
240 240 smatcher.matchfn = composedmatchfn
241 241
242 242 return smatcher
243 243
244 244 def standin(filename):
245 245 '''Return the repo-relative path to the standin for the specified big
246 246 file.'''
247 247 # Notes:
248 248 # 1) Some callers want an absolute path, but for instance addlargefiles
249 249 # needs it repo-relative so it can be passed to repo[None].add(). So
250 250 # leave it up to the caller to use repo.wjoin() to get an absolute path.
251 251 # 2) Join with '/' because that's what dirstate always uses, even on
252 252 # Windows. Change existing separator to '/' first in case we are
253 253 # passed filenames from an external source (like the command line).
254 254 return shortnameslash + util.pconvert(filename)
255 255
256 256 def isstandin(filename):
257 257 '''Return true if filename is a big file standin. filename must be
258 258 in Mercurial's internal form (slash-separated).'''
259 259 return filename.startswith(shortnameslash)
260 260
261 261 def splitstandin(filename):
262 262 # Split on / because that's what dirstate always uses, even on Windows.
263 263 # Change local separator to / first just in case we are passed filenames
264 264 # from an external source (like the command line).
265 265 bits = util.pconvert(filename).split('/', 1)
266 266 if len(bits) == 2 and bits[0] == shortname:
267 267 return bits[1]
268 268 else:
269 269 return None
270 270
271 271 def updatestandin(repo, standin):
272 272 file = repo.wjoin(splitstandin(standin))
273 273 if os.path.exists(file):
274 274 hash = hashfile(file)
275 275 executable = getexecutable(file)
276 276 writestandin(repo, standin, hash, executable)
277 277
278 278 def readstandin(repo, filename, node=None):
279 279 '''read hex hash from standin for filename at given node, or working
280 280 directory if no node is given'''
281 281 return repo[node][standin(filename)].data().strip()
282 282
283 283 def writestandin(repo, standin, hash, executable):
284 284 '''write hash to <repo.root>/<standin>'''
285 285 repo.wwrite(standin, hash + '\n', executable and 'x' or '')
286 286
287 287 def copyandhash(instream, outfile):
288 288 '''Read bytes from instream (iterable) and write them to outfile,
289 289 computing the SHA-1 hash of the data along the way. Return the hash.'''
290 290 hasher = util.sha1('')
291 291 for data in instream:
292 292 hasher.update(data)
293 293 outfile.write(data)
294 294 return hasher.hexdigest()
295 295
296 296 def hashrepofile(repo, file):
297 297 return hashfile(repo.wjoin(file))
298 298
299 299 def hashfile(file):
300 300 if not os.path.exists(file):
301 301 return ''
302 302 hasher = util.sha1('')
303 303 fd = open(file, 'rb')
304 304 for data in util.filechunkiter(fd, 128 * 1024):
305 305 hasher.update(data)
306 306 fd.close()
307 307 return hasher.hexdigest()
308 308
309 309 def getexecutable(filename):
310 310 mode = os.stat(filename).st_mode
311 311 return ((mode & stat.S_IXUSR) and
312 312 (mode & stat.S_IXGRP) and
313 313 (mode & stat.S_IXOTH))
314 314
315 315 def urljoin(first, second, *arg):
316 316 def join(left, right):
317 317 if not left.endswith('/'):
318 318 left += '/'
319 319 if right.startswith('/'):
320 320 right = right[1:]
321 321 return left + right
322 322
323 323 url = join(first, second)
324 324 for a in arg:
325 325 url = join(url, a)
326 326 return url
327 327
328 328 def hexsha1(data):
329 329 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
330 330 object data"""
331 331 h = util.sha1()
332 332 for chunk in util.filechunkiter(data):
333 333 h.update(chunk)
334 334 return h.hexdigest()
335 335
336 336 def httpsendfile(ui, filename):
337 337 return httpconnection.httpsendfile(ui, filename, 'rb')
338 338
339 339 def unixpath(path):
340 340 '''Return a version of path normalized for use with the lfdirstate.'''
341 341 return util.pconvert(os.path.normpath(path))
342 342
343 343 def islfilesrepo(repo):
344 344 if ('largefiles' in repo.requirements and
345 345 util.any(shortnameslash in f[0] for f in repo.store.datafiles())):
346 346 return True
347 347
348 348 return util.any(openlfdirstate(repo.ui, repo, False))
349 349
350 350 class storeprotonotcapable(Exception):
351 351 def __init__(self, storetypes):
352 352 self.storetypes = storetypes
353 353
354 354 def getstandinsstate(repo):
355 355 standins = []
356 356 matcher = getstandinmatcher(repo)
357 357 for standin in repo.dirstate.walk(matcher, [], False, False):
358 358 lfile = splitstandin(standin)
359 359 try:
360 360 hash = readstandin(repo, lfile)
361 361 except IOError:
362 362 hash = None
363 363 standins.append((lfile, hash))
364 364 return standins
365 365
366 366 def synclfdirstate(repo, lfdirstate, lfile, normallookup):
367 367 lfstandin = standin(lfile)
368 368 if lfstandin in repo.dirstate:
369 369 stat = repo.dirstate._map[lfstandin]
370 370 state, mtime = stat[0], stat[3]
371 371 else:
372 372 state, mtime = '?', -1
373 373 if state == 'n':
374 374 if normallookup or mtime < 0:
375 375 # state 'n' doesn't ensure 'clean' in this case
376 376 lfdirstate.normallookup(lfile)
377 377 else:
378 378 lfdirstate.normal(lfile)
379 379 elif state == 'm':
380 380 lfdirstate.normallookup(lfile)
381 381 elif state == 'r':
382 382 lfdirstate.remove(lfile)
383 383 elif state == 'a':
384 384 lfdirstate.add(lfile)
385 385 elif state == '?':
386 386 lfdirstate.drop(lfile)
387 387
388 388 def getlfilestoupdate(oldstandins, newstandins):
389 389 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
390 390 filelist = []
391 391 for f in changedstandins:
392 392 if f[0] not in filelist:
393 393 filelist.append(f[0])
394 394 return filelist
395 395
396 396 def getlfilestoupload(repo, missing, addfunc):
397 397 for n in missing:
398 398 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
399 399 ctx = repo[n]
400 400 files = set(ctx.files())
401 401 if len(parents) == 2:
402 402 mc = ctx.manifest()
403 403 mp1 = ctx.parents()[0].manifest()
404 404 mp2 = ctx.parents()[1].manifest()
405 405 for f in mp1:
406 406 if f not in mc:
407 407 files.add(f)
408 408 for f in mp2:
409 409 if f not in mc:
410 410 files.add(f)
411 411 for f in mc:
412 412 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
413 413 files.add(f)
414 414 for fn in files:
415 415 if isstandin(fn) and fn in ctx:
416 416 addfunc(fn, ctx[fn].data().strip())
General Comments 0
You need to be logged in to leave comments. Login now