##// END OF EJS Templates
largefiles: add some docstrings
Mads Kiilerich -
r28576:33bd9544 default
parent child Browse files
Show More
@@ -1,644 +1,655 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 stat
14 14 import copy
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, error
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 error.Abort(_('largefiles: size must be number (not %s)\n')
36 36 % lfsize)
37 37 if lfsize is None:
38 38 raise error.Abort(_('minimum size for largefiles must be specified'))
39 39 return lfsize
40 40
41 41 def link(src, dest):
42 """Try to create hardlink - if that fails, efficiently make a copy."""
42 43 util.makedirs(os.path.dirname(dest))
43 44 try:
44 45 util.oslink(src, dest)
45 46 except OSError:
46 47 # if hardlinks fail, fallback on atomic copy
47 48 dst = util.atomictempfile(dest)
48 49 for chunk in util.filechunkiter(open(src, 'rb')):
49 50 dst.write(chunk)
50 51 dst.close()
51 52 os.chmod(dest, os.stat(src).st_mode)
52 53
53 54 def usercachepath(ui, hash):
54 55 '''Return the correct location in the "global" largefiles cache for a file
55 56 with the given hash.
56 57 This cache is used for sharing of largefiles across repositories - both
57 58 to preserve download bandwidth and storage space.'''
58 59 return os.path.join(_usercachedir(ui), hash)
59 60
60 61 def _usercachedir(ui):
61 62 '''Return the location of the "global" largefiles cache.'''
62 63 path = ui.configpath(longname, 'usercache', None)
63 64 if path:
64 65 return path
65 66 if os.name == 'nt':
66 67 appdata = os.getenv('LOCALAPPDATA', os.getenv('APPDATA'))
67 68 if appdata:
68 69 return os.path.join(appdata, longname)
69 70 elif platform.system() == 'Darwin':
70 71 home = os.getenv('HOME')
71 72 if home:
72 73 return os.path.join(home, 'Library', 'Caches', longname)
73 74 elif os.name == 'posix':
74 75 path = os.getenv('XDG_CACHE_HOME')
75 76 if path:
76 77 return os.path.join(path, longname)
77 78 home = os.getenv('HOME')
78 79 if home:
79 80 return os.path.join(home, '.cache', longname)
80 81 else:
81 82 raise error.Abort(_('unknown operating system: %s\n') % os.name)
82 83 raise error.Abort(_('unknown %s usercache location\n') % longname)
83 84
84 85 def inusercache(ui, hash):
85 86 path = usercachepath(ui, hash)
86 87 return os.path.exists(path)
87 88
88 89 def findfile(repo, hash):
90 '''Return store path of the largefile with the specified hash.
91 As a side effect, the file might be linked from user cache.
92 Return None if the file can't be found locally.'''
89 93 path, exists = findstorepath(repo, hash)
90 94 if exists:
91 95 repo.ui.note(_('found %s in store\n') % hash)
92 96 return path
93 97 elif inusercache(repo.ui, hash):
94 98 repo.ui.note(_('found %s in system cache\n') % hash)
95 99 path = storepath(repo, hash)
96 100 link(usercachepath(repo.ui, hash), path)
97 101 return path
98 102 return None
99 103
100 104 class largefilesdirstate(dirstate.dirstate):
101 105 def __getitem__(self, key):
102 106 return super(largefilesdirstate, self).__getitem__(unixpath(key))
103 107 def normal(self, f):
104 108 return super(largefilesdirstate, self).normal(unixpath(f))
105 109 def remove(self, f):
106 110 return super(largefilesdirstate, self).remove(unixpath(f))
107 111 def add(self, f):
108 112 return super(largefilesdirstate, self).add(unixpath(f))
109 113 def drop(self, f):
110 114 return super(largefilesdirstate, self).drop(unixpath(f))
111 115 def forget(self, f):
112 116 return super(largefilesdirstate, self).forget(unixpath(f))
113 117 def normallookup(self, f):
114 118 return super(largefilesdirstate, self).normallookup(unixpath(f))
115 119 def _ignore(self, f):
116 120 return False
117 121 def write(self, tr=False):
118 122 # (1) disable PENDING mode always
119 123 # (lfdirstate isn't yet managed as a part of the transaction)
120 124 # (2) avoid develwarn 'use dirstate.write with ....'
121 125 super(largefilesdirstate, self).write(None)
122 126
123 127 def openlfdirstate(ui, repo, create=True):
124 128 '''
125 129 Return a dirstate object that tracks largefiles: i.e. its root is
126 130 the repo root, but it is saved in .hg/largefiles/dirstate.
127 131 '''
128 132 vfs = repo.vfs
129 133 lfstoredir = longname
130 134 opener = scmutil.opener(vfs.join(lfstoredir))
131 135 lfdirstate = largefilesdirstate(opener, ui, repo.root,
132 136 repo.dirstate._validate)
133 137
134 138 # If the largefiles dirstate does not exist, populate and create
135 139 # it. This ensures that we create it on the first meaningful
136 140 # largefiles operation in a new clone.
137 141 if create and not vfs.exists(vfs.join(lfstoredir, 'dirstate')):
138 142 matcher = getstandinmatcher(repo)
139 143 standins = repo.dirstate.walk(matcher, [], False, False)
140 144
141 145 if len(standins) > 0:
142 146 vfs.makedirs(lfstoredir)
143 147
144 148 for standin in standins:
145 149 lfile = splitstandin(standin)
146 150 lfdirstate.normallookup(lfile)
147 151 return lfdirstate
148 152
149 153 def lfdirstatestatus(lfdirstate, repo):
150 154 wctx = repo['.']
151 155 match = match_.always(repo.root, repo.getcwd())
152 156 unsure, s = lfdirstate.status(match, [], False, False, False)
153 157 modified, clean = s.modified, s.clean
154 158 for lfile in unsure:
155 159 try:
156 160 fctx = wctx[standin(lfile)]
157 161 except LookupError:
158 162 fctx = None
159 163 if not fctx or fctx.data().strip() != hashfile(repo.wjoin(lfile)):
160 164 modified.append(lfile)
161 165 else:
162 166 clean.append(lfile)
163 167 lfdirstate.normal(lfile)
164 168 return s
165 169
166 170 def listlfiles(repo, rev=None, matcher=None):
167 171 '''return a list of largefiles in the working copy or the
168 172 specified changeset'''
169 173
170 174 if matcher is None:
171 175 matcher = getstandinmatcher(repo)
172 176
173 177 # ignore unknown files in working directory
174 178 return [splitstandin(f)
175 179 for f in repo[rev].walk(matcher)
176 180 if rev is not None or repo.dirstate[f] != '?']
177 181
178 182 def instore(repo, hash, forcelocal=False):
183 '''Return true if a largefile with the given hash exists in the user
184 cache.'''
179 185 return os.path.exists(storepath(repo, hash, forcelocal))
180 186
181 187 def storepath(repo, hash, forcelocal=False):
188 '''Return the correct location in the repository largefiles cache for a
189 file with the given hash.'''
182 190 if not forcelocal and repo.shared():
183 191 return repo.vfs.reljoin(repo.sharedpath, longname, hash)
184 192 return repo.join(longname, hash)
185 193
186 194 def findstorepath(repo, hash):
187 195 '''Search through the local store path(s) to find the file for the given
188 196 hash. If the file is not found, its path in the primary store is returned.
189 197 The return value is a tuple of (path, exists(path)).
190 198 '''
191 199 # For shared repos, the primary store is in the share source. But for
192 200 # backward compatibility, force a lookup in the local store if it wasn't
193 201 # found in the share source.
194 202 path = storepath(repo, hash, False)
195 203
196 204 if instore(repo, hash):
197 205 return (path, True)
198 206 elif repo.shared() and instore(repo, hash, True):
199 207 return storepath(repo, hash, True)
200 208
201 209 return (path, False)
202 210
203 211 def copyfromcache(repo, hash, filename):
204 212 '''Copy the specified largefile from the repo or system cache to
205 213 filename in the repository. Return true on success or false if the
206 214 file was not found in either cache (which should not happened:
207 215 this is meant to be called only after ensuring that the needed
208 216 largefile exists in the cache).'''
209 217 wvfs = repo.wvfs
210 218 path = findfile(repo, hash)
211 219 if path is None:
212 220 return False
213 221 wvfs.makedirs(wvfs.dirname(wvfs.join(filename)))
214 222 # The write may fail before the file is fully written, but we
215 223 # don't use atomic writes in the working copy.
216 224 with open(path, 'rb') as srcfd:
217 225 with wvfs(filename, 'wb') as destfd:
218 226 gothash = copyandhash(srcfd, destfd)
219 227 if gothash != hash:
220 228 repo.ui.warn(_('%s: data corruption in %s with hash %s\n')
221 229 % (filename, path, gothash))
222 230 wvfs.unlink(filename)
223 231 return False
224 232 return True
225 233
226 234 def copytostore(repo, rev, file, uploaded=False):
227 235 wvfs = repo.wvfs
228 236 hash = readstandin(repo, file, rev)
229 237 if instore(repo, hash):
230 238 return
231 239 if wvfs.exists(file):
232 240 copytostoreabsolute(repo, wvfs.join(file), hash)
233 241 else:
234 242 repo.ui.warn(_("%s: largefile %s not available from local store\n") %
235 243 (file, hash))
236 244
237 245 def copyalltostore(repo, node):
238 246 '''Copy all largefiles in a given revision to the store'''
239 247
240 248 ctx = repo[node]
241 249 for filename in ctx.files():
242 250 if isstandin(filename) and filename in ctx.manifest():
243 251 realfile = splitstandin(filename)
244 252 copytostore(repo, ctx.node(), realfile)
245 253
246 254
247 255 def copytostoreabsolute(repo, file, hash):
248 256 if inusercache(repo.ui, hash):
249 257 link(usercachepath(repo.ui, hash), storepath(repo, hash))
250 258 else:
251 259 util.makedirs(os.path.dirname(storepath(repo, hash)))
252 260 dst = util.atomictempfile(storepath(repo, hash),
253 261 createmode=repo.store.createmode)
254 262 for chunk in util.filechunkiter(open(file, 'rb')):
255 263 dst.write(chunk)
256 264 dst.close()
257 265 linktousercache(repo, hash)
258 266
259 267 def linktousercache(repo, hash):
268 '''Link / copy the largefile with the specified hash from the store
269 to the cache.'''
260 270 path = usercachepath(repo.ui, hash)
261 271 link(storepath(repo, hash), path)
262 272
263 273 def getstandinmatcher(repo, rmatcher=None):
264 274 '''Return a match object that applies rmatcher to the standin directory'''
265 275 wvfs = repo.wvfs
266 276 standindir = shortname
267 277
268 278 # no warnings about missing files or directories
269 279 badfn = lambda f, msg: None
270 280
271 281 if rmatcher and not rmatcher.always():
272 282 pats = [wvfs.join(standindir, pat) for pat in rmatcher.files()]
273 283 if not pats:
274 284 pats = [wvfs.join(standindir)]
275 285 match = scmutil.match(repo[None], pats, badfn=badfn)
276 286 # if pats is empty, it would incorrectly always match, so clear _always
277 287 match._always = False
278 288 else:
279 289 # no patterns: relative to repo root
280 290 match = scmutil.match(repo[None], [wvfs.join(standindir)], badfn=badfn)
281 291 return match
282 292
283 293 def composestandinmatcher(repo, rmatcher):
284 294 '''Return a matcher that accepts standins corresponding to the
285 295 files accepted by rmatcher. Pass the list of files in the matcher
286 296 as the paths specified by the user.'''
287 297 smatcher = getstandinmatcher(repo, rmatcher)
288 298 isstandin = smatcher.matchfn
289 299 def composedmatchfn(f):
290 300 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
291 301 smatcher.matchfn = composedmatchfn
292 302
293 303 return smatcher
294 304
295 305 def standin(filename):
296 306 '''Return the repo-relative path to the standin for the specified big
297 307 file.'''
298 308 # Notes:
299 309 # 1) Some callers want an absolute path, but for instance addlargefiles
300 310 # needs it repo-relative so it can be passed to repo[None].add(). So
301 311 # leave it up to the caller to use repo.wjoin() to get an absolute path.
302 312 # 2) Join with '/' because that's what dirstate always uses, even on
303 313 # Windows. Change existing separator to '/' first in case we are
304 314 # passed filenames from an external source (like the command line).
305 315 return shortnameslash + util.pconvert(filename)
306 316
307 317 def isstandin(filename):
308 318 '''Return true if filename is a big file standin. filename must be
309 319 in Mercurial's internal form (slash-separated).'''
310 320 return filename.startswith(shortnameslash)
311 321
312 322 def splitstandin(filename):
313 323 # Split on / because that's what dirstate always uses, even on Windows.
314 324 # Change local separator to / first just in case we are passed filenames
315 325 # from an external source (like the command line).
316 326 bits = util.pconvert(filename).split('/', 1)
317 327 if len(bits) == 2 and bits[0] == shortname:
318 328 return bits[1]
319 329 else:
320 330 return None
321 331
322 332 def updatestandin(repo, standin):
323 333 file = repo.wjoin(splitstandin(standin))
324 334 if repo.wvfs.exists(splitstandin(standin)):
325 335 hash = hashfile(file)
326 336 executable = getexecutable(file)
327 337 writestandin(repo, standin, hash, executable)
328 338 else:
329 339 raise error.Abort(_('%s: file not found!') % splitstandin(standin))
330 340
331 341 def readstandin(repo, filename, node=None):
332 342 '''read hex hash from standin for filename at given node, or working
333 343 directory if no node is given'''
334 344 return repo[node][standin(filename)].data().strip()
335 345
336 346 def writestandin(repo, standin, hash, executable):
337 347 '''write hash to <repo.root>/<standin>'''
338 348 repo.wwrite(standin, hash + '\n', executable and 'x' or '')
339 349
340 350 def copyandhash(instream, outfile):
341 351 '''Read bytes from instream (iterable) and write them to outfile,
342 352 computing the SHA-1 hash of the data along the way. Return the hash.'''
343 353 hasher = util.sha1('')
344 354 for data in instream:
345 355 hasher.update(data)
346 356 outfile.write(data)
347 357 return hasher.hexdigest()
348 358
349 359 def hashrepofile(repo, file):
350 360 return hashfile(repo.wjoin(file))
351 361
352 362 def hashfile(file):
353 363 if not os.path.exists(file):
354 364 return ''
355 365 hasher = util.sha1('')
356 366 fd = open(file, 'rb')
357 367 for data in util.filechunkiter(fd, 128 * 1024):
358 368 hasher.update(data)
359 369 fd.close()
360 370 return hasher.hexdigest()
361 371
362 372 def getexecutable(filename):
363 373 mode = os.stat(filename).st_mode
364 374 return ((mode & stat.S_IXUSR) and
365 375 (mode & stat.S_IXGRP) and
366 376 (mode & stat.S_IXOTH))
367 377
368 378 def urljoin(first, second, *arg):
369 379 def join(left, right):
370 380 if not left.endswith('/'):
371 381 left += '/'
372 382 if right.startswith('/'):
373 383 right = right[1:]
374 384 return left + right
375 385
376 386 url = join(first, second)
377 387 for a in arg:
378 388 url = join(url, a)
379 389 return url
380 390
381 391 def hexsha1(data):
382 392 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
383 393 object data"""
384 394 h = util.sha1()
385 395 for chunk in util.filechunkiter(data):
386 396 h.update(chunk)
387 397 return h.hexdigest()
388 398
389 399 def httpsendfile(ui, filename):
390 400 return httpconnection.httpsendfile(ui, filename, 'rb')
391 401
392 402 def unixpath(path):
393 403 '''Return a version of path normalized for use with the lfdirstate.'''
394 404 return util.pconvert(os.path.normpath(path))
395 405
396 406 def islfilesrepo(repo):
407 '''Return true if the repo is a largefile repo.'''
397 408 if ('largefiles' in repo.requirements and
398 409 any(shortnameslash in f[0] for f in repo.store.datafiles())):
399 410 return True
400 411
401 412 return any(openlfdirstate(repo.ui, repo, False))
402 413
403 414 class storeprotonotcapable(Exception):
404 415 def __init__(self, storetypes):
405 416 self.storetypes = storetypes
406 417
407 418 def getstandinsstate(repo):
408 419 standins = []
409 420 matcher = getstandinmatcher(repo)
410 421 for standin in repo.dirstate.walk(matcher, [], False, False):
411 422 lfile = splitstandin(standin)
412 423 try:
413 424 hash = readstandin(repo, lfile)
414 425 except IOError:
415 426 hash = None
416 427 standins.append((lfile, hash))
417 428 return standins
418 429
419 430 def synclfdirstate(repo, lfdirstate, lfile, normallookup):
420 431 lfstandin = standin(lfile)
421 432 if lfstandin in repo.dirstate:
422 433 stat = repo.dirstate._map[lfstandin]
423 434 state, mtime = stat[0], stat[3]
424 435 else:
425 436 state, mtime = '?', -1
426 437 if state == 'n':
427 438 if (normallookup or mtime < 0 or
428 439 not repo.wvfs.exists(lfile)):
429 440 # state 'n' doesn't ensure 'clean' in this case
430 441 lfdirstate.normallookup(lfile)
431 442 else:
432 443 lfdirstate.normal(lfile)
433 444 elif state == 'm':
434 445 lfdirstate.normallookup(lfile)
435 446 elif state == 'r':
436 447 lfdirstate.remove(lfile)
437 448 elif state == 'a':
438 449 lfdirstate.add(lfile)
439 450 elif state == '?':
440 451 lfdirstate.drop(lfile)
441 452
442 453 def markcommitted(orig, ctx, node):
443 454 repo = ctx.repo()
444 455
445 456 orig(node)
446 457
447 458 # ATTENTION: "ctx.files()" may differ from "repo[node].files()"
448 459 # because files coming from the 2nd parent are omitted in the latter.
449 460 #
450 461 # The former should be used to get targets of "synclfdirstate",
451 462 # because such files:
452 463 # - are marked as "a" by "patch.patch()" (e.g. via transplant), and
453 464 # - have to be marked as "n" after commit, but
454 465 # - aren't listed in "repo[node].files()"
455 466
456 467 lfdirstate = openlfdirstate(repo.ui, repo)
457 468 for f in ctx.files():
458 469 if isstandin(f):
459 470 lfile = splitstandin(f)
460 471 synclfdirstate(repo, lfdirstate, lfile, False)
461 472 lfdirstate.write()
462 473
463 474 # As part of committing, copy all of the largefiles into the cache.
464 475 copyalltostore(repo, node)
465 476
466 477 def getlfilestoupdate(oldstandins, newstandins):
467 478 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
468 479 filelist = []
469 480 for f in changedstandins:
470 481 if f[0] not in filelist:
471 482 filelist.append(f[0])
472 483 return filelist
473 484
474 485 def getlfilestoupload(repo, missing, addfunc):
475 486 for i, n in enumerate(missing):
476 487 repo.ui.progress(_('finding outgoing largefiles'), i,
477 488 unit=_('revisions'), total=len(missing))
478 489 parents = [p for p in repo.changelog.parents(n) if p != node.nullid]
479 490
480 491 oldlfstatus = repo.lfstatus
481 492 repo.lfstatus = False
482 493 try:
483 494 ctx = repo[n]
484 495 finally:
485 496 repo.lfstatus = oldlfstatus
486 497
487 498 files = set(ctx.files())
488 499 if len(parents) == 2:
489 500 mc = ctx.manifest()
490 501 mp1 = ctx.parents()[0].manifest()
491 502 mp2 = ctx.parents()[1].manifest()
492 503 for f in mp1:
493 504 if f not in mc:
494 505 files.add(f)
495 506 for f in mp2:
496 507 if f not in mc:
497 508 files.add(f)
498 509 for f in mc:
499 510 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
500 511 files.add(f)
501 512 for fn in files:
502 513 if isstandin(fn) and fn in ctx:
503 514 addfunc(fn, ctx[fn].data().strip())
504 515 repo.ui.progress(_('finding outgoing largefiles'), None)
505 516
506 517 def updatestandinsbymatch(repo, match):
507 518 '''Update standins in the working directory according to specified match
508 519
509 520 This returns (possibly modified) ``match`` object to be used for
510 521 subsequent commit process.
511 522 '''
512 523
513 524 ui = repo.ui
514 525
515 526 # Case 1: user calls commit with no specific files or
516 527 # include/exclude patterns: refresh and commit all files that
517 528 # are "dirty".
518 529 if match is None or match.always():
519 530 # Spend a bit of time here to get a list of files we know
520 531 # are modified so we can compare only against those.
521 532 # It can cost a lot of time (several seconds)
522 533 # otherwise to update all standins if the largefiles are
523 534 # large.
524 535 lfdirstate = openlfdirstate(ui, repo)
525 536 dirtymatch = match_.always(repo.root, repo.getcwd())
526 537 unsure, s = lfdirstate.status(dirtymatch, [], False, False,
527 538 False)
528 539 modifiedfiles = unsure + s.modified + s.added + s.removed
529 540 lfiles = listlfiles(repo)
530 541 # this only loops through largefiles that exist (not
531 542 # removed/renamed)
532 543 for lfile in lfiles:
533 544 if lfile in modifiedfiles:
534 545 if repo.wvfs.exists(standin(lfile)):
535 546 # this handles the case where a rebase is being
536 547 # performed and the working copy is not updated
537 548 # yet.
538 549 if repo.wvfs.exists(lfile):
539 550 updatestandin(repo,
540 551 standin(lfile))
541 552
542 553 return match
543 554
544 555 lfiles = listlfiles(repo)
545 556 match._files = repo._subdirlfs(match.files(), lfiles)
546 557
547 558 # Case 2: user calls commit with specified patterns: refresh
548 559 # any matching big files.
549 560 smatcher = composestandinmatcher(repo, match)
550 561 standins = repo.dirstate.walk(smatcher, [], False, False)
551 562
552 563 # No matching big files: get out of the way and pass control to
553 564 # the usual commit() method.
554 565 if not standins:
555 566 return match
556 567
557 568 # Refresh all matching big files. It's possible that the
558 569 # commit will end up failing, in which case the big files will
559 570 # stay refreshed. No harm done: the user modified them and
560 571 # asked to commit them, so sooner or later we're going to
561 572 # refresh the standins. Might as well leave them refreshed.
562 573 lfdirstate = openlfdirstate(ui, repo)
563 574 for fstandin in standins:
564 575 lfile = splitstandin(fstandin)
565 576 if lfdirstate[lfile] != 'r':
566 577 updatestandin(repo, fstandin)
567 578
568 579 # Cook up a new matcher that only matches regular files or
569 580 # standins corresponding to the big files requested by the
570 581 # user. Have to modify _files to prevent commit() from
571 582 # complaining "not tracked" for big files.
572 583 match = copy.copy(match)
573 584 origmatchfn = match.matchfn
574 585
575 586 # Check both the list of largefiles and the list of
576 587 # standins because if a largefile was removed, it
577 588 # won't be in the list of largefiles at this point
578 589 match._files += sorted(standins)
579 590
580 591 actualfiles = []
581 592 for f in match._files:
582 593 fstandin = standin(f)
583 594
584 595 # For largefiles, only one of the normal and standin should be
585 596 # committed (except if one of them is a remove). In the case of a
586 597 # standin removal, drop the normal file if it is unknown to dirstate.
587 598 # Thus, skip plain largefile names but keep the standin.
588 599 if f in lfiles or fstandin in standins:
589 600 if repo.dirstate[fstandin] != 'r':
590 601 if repo.dirstate[f] != 'r':
591 602 continue
592 603 elif repo.dirstate[f] == '?':
593 604 continue
594 605
595 606 actualfiles.append(f)
596 607 match._files = actualfiles
597 608
598 609 def matchfn(f):
599 610 if origmatchfn(f):
600 611 return f not in lfiles
601 612 else:
602 613 return f in standins
603 614
604 615 match.matchfn = matchfn
605 616
606 617 return match
607 618
608 619 class automatedcommithook(object):
609 620 '''Stateful hook to update standins at the 1st commit of resuming
610 621
611 622 For efficiency, updating standins in the working directory should
612 623 be avoided while automated committing (like rebase, transplant and
613 624 so on), because they should be updated before committing.
614 625
615 626 But the 1st commit of resuming automated committing (e.g. ``rebase
616 627 --continue``) should update them, because largefiles may be
617 628 modified manually.
618 629 '''
619 630 def __init__(self, resuming):
620 631 self.resuming = resuming
621 632
622 633 def __call__(self, repo, match):
623 634 if self.resuming:
624 635 self.resuming = False # avoids updating at subsequent commits
625 636 return updatestandinsbymatch(repo, match)
626 637 else:
627 638 return match
628 639
629 640 def getstatuswriter(ui, repo, forcibly=None):
630 641 '''Return the function to write largefiles specific status out
631 642
632 643 If ``forcibly`` is ``None``, this returns the last element of
633 644 ``repo._lfstatuswriters`` as "default" writer function.
634 645
635 646 Otherwise, this returns the function to always write out (or
636 647 ignore if ``not forcibly``) status.
637 648 '''
638 649 if forcibly is None and util.safehasattr(repo, '_largefilesenabled'):
639 650 return repo._lfstatuswriters[-1]
640 651 else:
641 652 if forcibly:
642 653 return ui.status # forcibly WRITE OUT
643 654 else:
644 655 return lambda *msg, **opts: None # forcibly IGNORE
@@ -1,175 +1,178 b''
1 1 # Copyright 2011 Fog Creek Software
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 import os
7 7 import urllib2
8 8 import re
9 9
10 10 from mercurial import error, httppeer, util, wireproto
11 11 from mercurial.i18n import _
12 12
13 13 import lfutil
14 14
15 15 LARGEFILES_REQUIRED_MSG = ('\nThis repository uses the largefiles extension.'
16 16 '\n\nPlease enable it in your Mercurial config '
17 17 'file.\n')
18 18
19 19 # these will all be replaced by largefiles.uisetup
20 20 capabilitiesorig = None
21 21 ssholdcallstream = None
22 22 httpoldcallstream = None
23 23
24 24 def putlfile(repo, proto, sha):
25 '''Put a largefile into a repository's local store and into the
26 user cache.'''
25 '''Server command for putting a largefile into a repository's local store
26 and into the user cache.'''
27 27 proto.redirect()
28 28
29 29 path = lfutil.storepath(repo, sha)
30 30 util.makedirs(os.path.dirname(path))
31 31 tmpfp = util.atomictempfile(path, createmode=repo.store.createmode)
32 32
33 33 try:
34 34 proto.getfile(tmpfp)
35 35 tmpfp._fp.seek(0)
36 36 if sha != lfutil.hexsha1(tmpfp._fp):
37 37 raise IOError(0, _('largefile contents do not match hash'))
38 38 tmpfp.close()
39 39 lfutil.linktousercache(repo, sha)
40 40 except IOError as e:
41 41 repo.ui.warn(_('largefiles: failed to put %s into store: %s\n') %
42 42 (sha, e.strerror))
43 43 return wireproto.pushres(1)
44 44 finally:
45 45 tmpfp.discard()
46 46
47 47 return wireproto.pushres(0)
48 48
49 49 def getlfile(repo, proto, sha):
50 '''Retrieve a largefile from the repository-local cache or system
51 cache.'''
50 '''Server command for retrieving a largefile from the repository-local
51 cache or user cache.'''
52 52 filename = lfutil.findfile(repo, sha)
53 53 if not filename:
54 54 raise error.Abort(_('requested largefile %s not present in cache')
55 55 % sha)
56 56 f = open(filename, 'rb')
57 57 length = os.fstat(f.fileno())[6]
58 58
59 59 # Since we can't set an HTTP content-length header here, and
60 60 # Mercurial core provides no way to give the length of a streamres
61 61 # (and reading the entire file into RAM would be ill-advised), we
62 62 # just send the length on the first line of the response, like the
63 63 # ssh proto does for string responses.
64 64 def generator():
65 65 yield '%d\n' % length
66 66 for chunk in util.filechunkiter(f):
67 67 yield chunk
68 68 return wireproto.streamres(generator())
69 69
70 70 def statlfile(repo, proto, sha):
71 '''Return '2\n' if the largefile is missing, '0\n' if it seems to be in
72 good condition.
71 '''Server command for checking if a largefile is present - returns '2\n' if
72 the largefile is missing, '0\n' if it seems to be in good condition.
73 73
74 74 The value 1 is reserved for mismatched checksum, but that is too expensive
75 75 to be verified on every stat and must be caught be running 'hg verify'
76 76 server side.'''
77 77 filename = lfutil.findfile(repo, sha)
78 78 if not filename:
79 79 return '2\n'
80 80 return '0\n'
81 81
82 82 def wirereposetup(ui, repo):
83 83 class lfileswirerepository(repo.__class__):
84 84 def putlfile(self, sha, fd):
85 85 # unfortunately, httprepository._callpush tries to convert its
86 86 # input file-like into a bundle before sending it, so we can't use
87 87 # it ...
88 88 if issubclass(self.__class__, httppeer.httppeer):
89 89 res = self._call('putlfile', data=fd, sha=sha,
90 90 headers={'content-type':'application/mercurial-0.1'})
91 91 try:
92 92 d, output = res.split('\n', 1)
93 93 for l in output.splitlines(True):
94 94 self.ui.warn(_('remote: '), l) # assume l ends with \n
95 95 return int(d)
96 96 except ValueError:
97 97 self.ui.warn(_('unexpected putlfile response: %r\n') % res)
98 98 return 1
99 99 # ... but we can't use sshrepository._call because the data=
100 100 # argument won't get sent, and _callpush does exactly what we want
101 101 # in this case: send the data straight through
102 102 else:
103 103 try:
104 104 ret, output = self._callpush("putlfile", fd, sha=sha)
105 105 if ret == "":
106 106 raise error.ResponseError(_('putlfile failed:'),
107 107 output)
108 108 return int(ret)
109 109 except IOError:
110 110 return 1
111 111 except ValueError:
112 112 raise error.ResponseError(
113 113 _('putlfile failed (unexpected response):'), ret)
114 114
115 115 def getlfile(self, sha):
116 116 """returns an iterable with the chunks of the file with sha sha"""
117 117 stream = self._callstream("getlfile", sha=sha)
118 118 length = stream.readline()
119 119 try:
120 120 length = int(length)
121 121 except ValueError:
122 122 self._abort(error.ResponseError(_("unexpected response:"),
123 123 length))
124 124
125 125 # SSH streams will block if reading more than length
126 126 for chunk in util.filechunkiter(stream, 128 * 1024, length):
127 127 yield chunk
128 128 # HTTP streams must hit the end to process the last empty
129 129 # chunk of Chunked-Encoding so the connection can be reused.
130 130 if issubclass(self.__class__, httppeer.httppeer):
131 131 chunk = stream.read(1)
132 132 if chunk:
133 133 self._abort(error.ResponseError(_("unexpected response:"),
134 134 chunk))
135 135
136 136 @wireproto.batchable
137 137 def statlfile(self, sha):
138 138 f = wireproto.future()
139 139 result = {'sha': sha}
140 140 yield result, f
141 141 try:
142 142 yield int(f.value)
143 143 except (ValueError, urllib2.HTTPError):
144 144 # If the server returns anything but an integer followed by a
145 145 # newline, newline, it's not speaking our language; if we get
146 146 # an HTTP error, we can't be sure the largefile is present;
147 147 # either way, consider it missing.
148 148 yield 2
149 149
150 150 repo.__class__ = lfileswirerepository
151 151
152 152 # advertise the largefiles=serve capability
153 153 def capabilities(repo, proto):
154 '''Wrap server command to announce largefile server capability'''
154 155 return capabilitiesorig(repo, proto) + ' largefiles=serve'
155 156
156 157 def heads(repo, proto):
158 '''Wrap server command - largefile capable clients will know to call
159 lheads instead'''
157 160 if lfutil.islfilesrepo(repo):
158 161 return wireproto.ooberror(LARGEFILES_REQUIRED_MSG)
159 162 return wireproto.heads(repo, proto)
160 163
161 164 def sshrepocallstream(self, cmd, **args):
162 165 if cmd == 'heads' and self.capable('largefiles'):
163 166 cmd = 'lheads'
164 167 if cmd == 'batch' and self.capable('largefiles'):
165 168 args['cmds'] = args['cmds'].replace('heads ', 'lheads ')
166 169 return ssholdcallstream(self, cmd, **args)
167 170
168 171 headsre = re.compile(r'(^|;)heads\b')
169 172
170 173 def httprepocallstream(self, cmd, **args):
171 174 if cmd == 'heads' and self.capable('largefiles'):
172 175 cmd = 'lheads'
173 176 if cmd == 'batch' and self.capable('largefiles'):
174 177 args['cmds'] = headsre.sub('lheads', args['cmds'])
175 178 return httpoldcallstream(self, cmd, **args)
General Comments 0
You need to be logged in to leave comments. Login now