##// END OF EJS Templates
large-files: make sure we write newly initialized standin file early...
marmoute -
r51011:9b49809e default
parent child Browse files
Show More
@@ -1,817 +1,819 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 contextlib
12 12 import copy
13 13 import os
14 14 import stat
15 15
16 16 from mercurial.i18n import _
17 17 from mercurial.node import hex
18 18 from mercurial.pycompat import open
19 19
20 20 from mercurial import (
21 21 dirstate,
22 22 encoding,
23 23 error,
24 24 httpconnection,
25 25 match as matchmod,
26 26 pycompat,
27 27 requirements,
28 28 scmutil,
29 29 sparse,
30 30 util,
31 31 vfs as vfsmod,
32 32 )
33 33 from mercurial.utils import hashutil
34 34 from mercurial.dirstateutils import timestamp
35 35
36 36 shortname = b'.hglf'
37 37 shortnameslash = shortname + b'/'
38 38 longname = b'largefiles'
39 39
40 40 # -- Private worker functions ------------------------------------------
41 41
42 42
43 43 @contextlib.contextmanager
44 44 def lfstatus(repo, value=True):
45 45 oldvalue = getattr(repo, 'lfstatus', False)
46 46 repo.lfstatus = value
47 47 try:
48 48 yield
49 49 finally:
50 50 repo.lfstatus = oldvalue
51 51
52 52
53 53 def getminsize(ui, assumelfiles, opt, default=10):
54 54 lfsize = opt
55 55 if not lfsize and assumelfiles:
56 56 lfsize = ui.config(longname, b'minsize', default=default)
57 57 if lfsize:
58 58 try:
59 59 lfsize = float(lfsize)
60 60 except ValueError:
61 61 raise error.Abort(
62 62 _(b'largefiles: size must be number (not %s)\n') % lfsize
63 63 )
64 64 if lfsize is None:
65 65 raise error.Abort(_(b'minimum size for largefiles must be specified'))
66 66 return lfsize
67 67
68 68
69 69 def link(src, dest):
70 70 """Try to create hardlink - if that fails, efficiently make a copy."""
71 71 util.makedirs(os.path.dirname(dest))
72 72 try:
73 73 util.oslink(src, dest)
74 74 except OSError:
75 75 # if hardlinks fail, fallback on atomic copy
76 76 with open(src, b'rb') as srcf, util.atomictempfile(dest) as dstf:
77 77 for chunk in util.filechunkiter(srcf):
78 78 dstf.write(chunk)
79 79 os.chmod(dest, os.stat(src).st_mode)
80 80
81 81
82 82 def usercachepath(ui, hash):
83 83 """Return the correct location in the "global" largefiles cache for a file
84 84 with the given hash.
85 85 This cache is used for sharing of largefiles across repositories - both
86 86 to preserve download bandwidth and storage space."""
87 87 return os.path.join(_usercachedir(ui), hash)
88 88
89 89
90 90 def _usercachedir(ui, name=longname):
91 91 '''Return the location of the "global" largefiles cache.'''
92 92 path = ui.configpath(name, b'usercache')
93 93 if path:
94 94 return path
95 95
96 96 hint = None
97 97
98 98 if pycompat.iswindows:
99 99 appdata = encoding.environ.get(
100 100 b'LOCALAPPDATA', encoding.environ.get(b'APPDATA')
101 101 )
102 102 if appdata:
103 103 return os.path.join(appdata, name)
104 104
105 105 hint = _(b"define %s or %s in the environment, or set %s.usercache") % (
106 106 b"LOCALAPPDATA",
107 107 b"APPDATA",
108 108 name,
109 109 )
110 110 elif pycompat.isdarwin:
111 111 home = encoding.environ.get(b'HOME')
112 112 if home:
113 113 return os.path.join(home, b'Library', b'Caches', name)
114 114
115 115 hint = _(b"define %s in the environment, or set %s.usercache") % (
116 116 b"HOME",
117 117 name,
118 118 )
119 119 elif pycompat.isposix:
120 120 path = encoding.environ.get(b'XDG_CACHE_HOME')
121 121 if path:
122 122 return os.path.join(path, name)
123 123 home = encoding.environ.get(b'HOME')
124 124 if home:
125 125 return os.path.join(home, b'.cache', name)
126 126
127 127 hint = _(b"define %s or %s in the environment, or set %s.usercache") % (
128 128 b"XDG_CACHE_HOME",
129 129 b"HOME",
130 130 name,
131 131 )
132 132 else:
133 133 raise error.Abort(
134 134 _(b'unknown operating system: %s\n') % pycompat.osname
135 135 )
136 136
137 137 raise error.Abort(_(b'unknown %s usercache location') % name, hint=hint)
138 138
139 139
140 140 def inusercache(ui, hash):
141 141 path = usercachepath(ui, hash)
142 142 return os.path.exists(path)
143 143
144 144
145 145 def findfile(repo, hash):
146 146 """Return store path of the largefile with the specified hash.
147 147 As a side effect, the file might be linked from user cache.
148 148 Return None if the file can't be found locally."""
149 149 path, exists = findstorepath(repo, hash)
150 150 if exists:
151 151 repo.ui.note(_(b'found %s in store\n') % hash)
152 152 return path
153 153 elif inusercache(repo.ui, hash):
154 154 repo.ui.note(_(b'found %s in system cache\n') % hash)
155 155 path = storepath(repo, hash)
156 156 link(usercachepath(repo.ui, hash), path)
157 157 return path
158 158 return None
159 159
160 160
161 161 class largefilesdirstate(dirstate.dirstate):
162 162 _large_file_dirstate = True
163 163
164 164 def __getitem__(self, key):
165 165 return super(largefilesdirstate, self).__getitem__(unixpath(key))
166 166
167 167 def set_tracked(self, f):
168 168 return super(largefilesdirstate, self).set_tracked(unixpath(f))
169 169
170 170 def set_untracked(self, f):
171 171 return super(largefilesdirstate, self).set_untracked(unixpath(f))
172 172
173 173 def normal(self, f, parentfiledata=None):
174 174 # not sure if we should pass the `parentfiledata` down or throw it
175 175 # away. So throwing it away to stay on the safe side.
176 176 return super(largefilesdirstate, self).normal(unixpath(f))
177 177
178 178 def remove(self, f):
179 179 return super(largefilesdirstate, self).remove(unixpath(f))
180 180
181 181 def add(self, f):
182 182 return super(largefilesdirstate, self).add(unixpath(f))
183 183
184 184 def drop(self, f):
185 185 return super(largefilesdirstate, self).drop(unixpath(f))
186 186
187 187 def forget(self, f):
188 188 return super(largefilesdirstate, self).forget(unixpath(f))
189 189
190 190 def normallookup(self, f):
191 191 return super(largefilesdirstate, self).normallookup(unixpath(f))
192 192
193 193 def _ignore(self, f):
194 194 return False
195 195
196 196 def write(self, tr):
197 197 # (1) disable PENDING mode always
198 198 # (lfdirstate isn't yet managed as a part of the transaction)
199 199 # (2) avoid develwarn 'use dirstate.write with ....'
200 200 if tr:
201 201 tr.addbackup(b'largefiles/dirstate', location=b'plain')
202 202 super(largefilesdirstate, self).write(None)
203 203
204 204
205 205 def openlfdirstate(ui, repo, create=True):
206 206 """
207 207 Return a dirstate object that tracks largefiles: i.e. its root is
208 208 the repo root, but it is saved in .hg/largefiles/dirstate.
209 209
210 210 If a dirstate object already exists and is being used for a 'changing_*'
211 211 context, it will be returned.
212 212 """
213 213 sub_dirstate = getattr(repo.dirstate, '_sub_dirstate', None)
214 214 if sub_dirstate is not None:
215 215 return sub_dirstate
216 216 vfs = repo.vfs
217 217 lfstoredir = longname
218 218 opener = vfsmod.vfs(vfs.join(lfstoredir))
219 219 use_dirstate_v2 = requirements.DIRSTATE_V2_REQUIREMENT in repo.requirements
220 220 lfdirstate = largefilesdirstate(
221 221 opener,
222 222 ui,
223 223 repo.root,
224 224 repo.dirstate._validate,
225 225 lambda: sparse.matcher(repo),
226 226 repo.nodeconstants,
227 227 use_dirstate_v2,
228 228 )
229 229
230 230 # If the largefiles dirstate does not exist, populate and create
231 231 # it. This ensures that we create it on the first meaningful
232 232 # largefiles operation in a new clone.
233 233 if create and not vfs.exists(vfs.join(lfstoredir, b'dirstate')):
234 234 try:
235 235 with repo.wlock(wait=False):
236 236 matcher = getstandinmatcher(repo)
237 237 standins = repo.dirstate.walk(
238 238 matcher, subrepos=[], unknown=False, ignored=False
239 239 )
240 240
241 241 if len(standins) > 0:
242 242 vfs.makedirs(lfstoredir)
243 243
244 244 for standin in standins:
245 245 lfile = splitstandin(standin)
246 246 lfdirstate.hacky_extension_update_file(
247 247 lfile,
248 248 p1_tracked=True,
249 249 wc_tracked=True,
250 250 possibly_dirty=True,
251 251 )
252 # avoid getting dirty dirstate before other operations
253 lfdirstate.write(repo.currenttransaction())
252 254 except error.LockError:
253 255 # Assume that whatever was holding the lock was important.
254 256 # If we were doing something important, we would already have
255 257 # either the lock or a largefile dirstate.
256 258 pass
257 259 return lfdirstate
258 260
259 261
260 262 def lfdirstatestatus(lfdirstate, repo):
261 263 pctx = repo[b'.']
262 264 match = matchmod.always()
263 265 unsure, s, mtime_boundary = lfdirstate.status(
264 266 match, subrepos=[], ignored=False, clean=False, unknown=False
265 267 )
266 268 modified, clean = s.modified, s.clean
267 269 wctx = repo[None]
268 270 for lfile in unsure:
269 271 try:
270 272 fctx = pctx[standin(lfile)]
271 273 except LookupError:
272 274 fctx = None
273 275 if not fctx or readasstandin(fctx) != hashfile(repo.wjoin(lfile)):
274 276 modified.append(lfile)
275 277 else:
276 278 clean.append(lfile)
277 279 st = wctx[lfile].lstat()
278 280 mode = st.st_mode
279 281 size = st.st_size
280 282 mtime = timestamp.reliable_mtime_of(st, mtime_boundary)
281 283 if mtime is not None:
282 284 cache_data = (mode, size, mtime)
283 285 lfdirstate.set_clean(lfile, cache_data)
284 286 return s
285 287
286 288
287 289 def listlfiles(repo, rev=None, matcher=None):
288 290 """return a list of largefiles in the working copy or the
289 291 specified changeset"""
290 292
291 293 if matcher is None:
292 294 matcher = getstandinmatcher(repo)
293 295
294 296 # ignore unknown files in working directory
295 297 return [
296 298 splitstandin(f)
297 299 for f in repo[rev].walk(matcher)
298 300 if rev is not None or repo.dirstate.get_entry(f).any_tracked
299 301 ]
300 302
301 303
302 304 def instore(repo, hash, forcelocal=False):
303 305 '''Return true if a largefile with the given hash exists in the store'''
304 306 return os.path.exists(storepath(repo, hash, forcelocal))
305 307
306 308
307 309 def storepath(repo, hash, forcelocal=False):
308 310 """Return the correct location in the repository largefiles store for a
309 311 file with the given hash."""
310 312 if not forcelocal and repo.shared():
311 313 return repo.vfs.reljoin(repo.sharedpath, longname, hash)
312 314 return repo.vfs.join(longname, hash)
313 315
314 316
315 317 def findstorepath(repo, hash):
316 318 """Search through the local store path(s) to find the file for the given
317 319 hash. If the file is not found, its path in the primary store is returned.
318 320 The return value is a tuple of (path, exists(path)).
319 321 """
320 322 # For shared repos, the primary store is in the share source. But for
321 323 # backward compatibility, force a lookup in the local store if it wasn't
322 324 # found in the share source.
323 325 path = storepath(repo, hash, False)
324 326
325 327 if instore(repo, hash):
326 328 return (path, True)
327 329 elif repo.shared() and instore(repo, hash, True):
328 330 return storepath(repo, hash, True), True
329 331
330 332 return (path, False)
331 333
332 334
333 335 def copyfromcache(repo, hash, filename):
334 336 """Copy the specified largefile from the repo or system cache to
335 337 filename in the repository. Return true on success or false if the
336 338 file was not found in either cache (which should not happened:
337 339 this is meant to be called only after ensuring that the needed
338 340 largefile exists in the cache)."""
339 341 wvfs = repo.wvfs
340 342 path = findfile(repo, hash)
341 343 if path is None:
342 344 return False
343 345 wvfs.makedirs(wvfs.dirname(wvfs.join(filename)))
344 346 # The write may fail before the file is fully written, but we
345 347 # don't use atomic writes in the working copy.
346 348 with open(path, b'rb') as srcfd, wvfs(filename, b'wb') as destfd:
347 349 gothash = copyandhash(util.filechunkiter(srcfd), destfd)
348 350 if gothash != hash:
349 351 repo.ui.warn(
350 352 _(b'%s: data corruption in %s with hash %s\n')
351 353 % (filename, path, gothash)
352 354 )
353 355 wvfs.unlink(filename)
354 356 return False
355 357 return True
356 358
357 359
358 360 def copytostore(repo, ctx, file, fstandin):
359 361 wvfs = repo.wvfs
360 362 hash = readasstandin(ctx[fstandin])
361 363 if instore(repo, hash):
362 364 return
363 365 if wvfs.exists(file):
364 366 copytostoreabsolute(repo, wvfs.join(file), hash)
365 367 else:
366 368 repo.ui.warn(
367 369 _(b"%s: largefile %s not available from local store\n")
368 370 % (file, hash)
369 371 )
370 372
371 373
372 374 def copyalltostore(repo, node):
373 375 '''Copy all largefiles in a given revision to the store'''
374 376
375 377 ctx = repo[node]
376 378 for filename in ctx.files():
377 379 realfile = splitstandin(filename)
378 380 if realfile is not None and filename in ctx.manifest():
379 381 copytostore(repo, ctx, realfile, filename)
380 382
381 383
382 384 def copytostoreabsolute(repo, file, hash):
383 385 if inusercache(repo.ui, hash):
384 386 link(usercachepath(repo.ui, hash), storepath(repo, hash))
385 387 else:
386 388 util.makedirs(os.path.dirname(storepath(repo, hash)))
387 389 with open(file, b'rb') as srcf:
388 390 with util.atomictempfile(
389 391 storepath(repo, hash), createmode=repo.store.createmode
390 392 ) as dstf:
391 393 for chunk in util.filechunkiter(srcf):
392 394 dstf.write(chunk)
393 395 linktousercache(repo, hash)
394 396
395 397
396 398 def linktousercache(repo, hash):
397 399 """Link / copy the largefile with the specified hash from the store
398 400 to the cache."""
399 401 path = usercachepath(repo.ui, hash)
400 402 link(storepath(repo, hash), path)
401 403
402 404
403 405 def getstandinmatcher(repo, rmatcher=None):
404 406 '''Return a match object that applies rmatcher to the standin directory'''
405 407 wvfs = repo.wvfs
406 408 standindir = shortname
407 409
408 410 # no warnings about missing files or directories
409 411 badfn = lambda f, msg: None
410 412
411 413 if rmatcher and not rmatcher.always():
412 414 pats = [wvfs.join(standindir, pat) for pat in rmatcher.files()]
413 415 if not pats:
414 416 pats = [wvfs.join(standindir)]
415 417 match = scmutil.match(repo[None], pats, badfn=badfn)
416 418 else:
417 419 # no patterns: relative to repo root
418 420 match = scmutil.match(repo[None], [wvfs.join(standindir)], badfn=badfn)
419 421 return match
420 422
421 423
422 424 def composestandinmatcher(repo, rmatcher):
423 425 """Return a matcher that accepts standins corresponding to the
424 426 files accepted by rmatcher. Pass the list of files in the matcher
425 427 as the paths specified by the user."""
426 428 smatcher = getstandinmatcher(repo, rmatcher)
427 429 isstandin = smatcher.matchfn
428 430
429 431 def composedmatchfn(f):
430 432 return isstandin(f) and rmatcher.matchfn(splitstandin(f))
431 433
432 434 smatcher.matchfn = composedmatchfn
433 435
434 436 return smatcher
435 437
436 438
437 439 def standin(filename):
438 440 """Return the repo-relative path to the standin for the specified big
439 441 file."""
440 442 # Notes:
441 443 # 1) Some callers want an absolute path, but for instance addlargefiles
442 444 # needs it repo-relative so it can be passed to repo[None].add(). So
443 445 # leave it up to the caller to use repo.wjoin() to get an absolute path.
444 446 # 2) Join with '/' because that's what dirstate always uses, even on
445 447 # Windows. Change existing separator to '/' first in case we are
446 448 # passed filenames from an external source (like the command line).
447 449 return shortnameslash + util.pconvert(filename)
448 450
449 451
450 452 def isstandin(filename):
451 453 """Return true if filename is a big file standin. filename must be
452 454 in Mercurial's internal form (slash-separated)."""
453 455 return filename.startswith(shortnameslash)
454 456
455 457
456 458 def splitstandin(filename):
457 459 # Split on / because that's what dirstate always uses, even on Windows.
458 460 # Change local separator to / first just in case we are passed filenames
459 461 # from an external source (like the command line).
460 462 bits = util.pconvert(filename).split(b'/', 1)
461 463 if len(bits) == 2 and bits[0] == shortname:
462 464 return bits[1]
463 465 else:
464 466 return None
465 467
466 468
467 469 def updatestandin(repo, lfile, standin):
468 470 """Re-calculate hash value of lfile and write it into standin
469 471
470 472 This assumes that "lfutil.standin(lfile) == standin", for efficiency.
471 473 """
472 474 file = repo.wjoin(lfile)
473 475 if repo.wvfs.exists(lfile):
474 476 hash = hashfile(file)
475 477 executable = getexecutable(file)
476 478 writestandin(repo, standin, hash, executable)
477 479 else:
478 480 raise error.Abort(_(b'%s: file not found!') % lfile)
479 481
480 482
481 483 def readasstandin(fctx):
482 484 """read hex hash from given filectx of standin file
483 485
484 486 This encapsulates how "standin" data is stored into storage layer."""
485 487 return fctx.data().strip()
486 488
487 489
488 490 def writestandin(repo, standin, hash, executable):
489 491 '''write hash to <repo.root>/<standin>'''
490 492 repo.wwrite(standin, hash + b'\n', executable and b'x' or b'')
491 493
492 494
493 495 def copyandhash(instream, outfile):
494 496 """Read bytes from instream (iterable) and write them to outfile,
495 497 computing the SHA-1 hash of the data along the way. Return the hash."""
496 498 hasher = hashutil.sha1(b'')
497 499 for data in instream:
498 500 hasher.update(data)
499 501 outfile.write(data)
500 502 return hex(hasher.digest())
501 503
502 504
503 505 def hashfile(file):
504 506 if not os.path.exists(file):
505 507 return b''
506 508 with open(file, b'rb') as fd:
507 509 return hexsha1(fd)
508 510
509 511
510 512 def getexecutable(filename):
511 513 mode = os.stat(filename).st_mode
512 514 return (
513 515 (mode & stat.S_IXUSR)
514 516 and (mode & stat.S_IXGRP)
515 517 and (mode & stat.S_IXOTH)
516 518 )
517 519
518 520
519 521 def urljoin(first, second, *arg):
520 522 def join(left, right):
521 523 if not left.endswith(b'/'):
522 524 left += b'/'
523 525 if right.startswith(b'/'):
524 526 right = right[1:]
525 527 return left + right
526 528
527 529 url = join(first, second)
528 530 for a in arg:
529 531 url = join(url, a)
530 532 return url
531 533
532 534
533 535 def hexsha1(fileobj):
534 536 """hexsha1 returns the hex-encoded sha1 sum of the data in the file-like
535 537 object data"""
536 538 h = hashutil.sha1()
537 539 for chunk in util.filechunkiter(fileobj):
538 540 h.update(chunk)
539 541 return hex(h.digest())
540 542
541 543
542 544 def httpsendfile(ui, filename):
543 545 return httpconnection.httpsendfile(ui, filename, b'rb')
544 546
545 547
546 548 def unixpath(path):
547 549 '''Return a version of path normalized for use with the lfdirstate.'''
548 550 return util.pconvert(os.path.normpath(path))
549 551
550 552
551 553 def islfilesrepo(repo):
552 554 '''Return true if the repo is a largefile repo.'''
553 555 if b'largefiles' in repo.requirements and any(
554 556 shortnameslash in f[1] for f in repo.store.datafiles()
555 557 ):
556 558 return True
557 559
558 560 return any(openlfdirstate(repo.ui, repo, False))
559 561
560 562
561 563 class storeprotonotcapable(Exception):
562 564 def __init__(self, storetypes):
563 565 self.storetypes = storetypes
564 566
565 567
566 568 def getstandinsstate(repo):
567 569 standins = []
568 570 matcher = getstandinmatcher(repo)
569 571 wctx = repo[None]
570 572 for standin in repo.dirstate.walk(
571 573 matcher, subrepos=[], unknown=False, ignored=False
572 574 ):
573 575 lfile = splitstandin(standin)
574 576 try:
575 577 hash = readasstandin(wctx[standin])
576 578 except IOError:
577 579 hash = None
578 580 standins.append((lfile, hash))
579 581 return standins
580 582
581 583
582 584 def synclfdirstate(repo, lfdirstate, lfile, normallookup):
583 585 lfstandin = standin(lfile)
584 586 if lfstandin not in repo.dirstate:
585 587 lfdirstate.hacky_extension_update_file(
586 588 lfile,
587 589 p1_tracked=False,
588 590 wc_tracked=False,
589 591 )
590 592 else:
591 593 entry = repo.dirstate.get_entry(lfstandin)
592 594 lfdirstate.hacky_extension_update_file(
593 595 lfile,
594 596 wc_tracked=entry.tracked,
595 597 p1_tracked=entry.p1_tracked,
596 598 p2_info=entry.p2_info,
597 599 possibly_dirty=True,
598 600 )
599 601
600 602
601 603 def markcommitted(orig, ctx, node):
602 604 repo = ctx.repo()
603 605
604 606 with repo.dirstate.changing_parents(repo):
605 607 orig(node)
606 608
607 609 # ATTENTION: "ctx.files()" may differ from "repo[node].files()"
608 610 # because files coming from the 2nd parent are omitted in the latter.
609 611 #
610 612 # The former should be used to get targets of "synclfdirstate",
611 613 # because such files:
612 614 # - are marked as "a" by "patch.patch()" (e.g. via transplant), and
613 615 # - have to be marked as "n" after commit, but
614 616 # - aren't listed in "repo[node].files()"
615 617
616 618 lfdirstate = openlfdirstate(repo.ui, repo)
617 619 for f in ctx.files():
618 620 lfile = splitstandin(f)
619 621 if lfile is not None:
620 622 synclfdirstate(repo, lfdirstate, lfile, False)
621 623
622 624 # As part of committing, copy all of the largefiles into the cache.
623 625 #
624 626 # Using "node" instead of "ctx" implies additional "repo[node]"
625 627 # lookup while copyalltostore(), but can omit redundant check for
626 628 # files comming from the 2nd parent, which should exist in store
627 629 # at merging.
628 630 copyalltostore(repo, node)
629 631
630 632
631 633 def getlfilestoupdate(oldstandins, newstandins):
632 634 changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
633 635 filelist = []
634 636 for f in changedstandins:
635 637 if f[0] not in filelist:
636 638 filelist.append(f[0])
637 639 return filelist
638 640
639 641
640 642 def getlfilestoupload(repo, missing, addfunc):
641 643 makeprogress = repo.ui.makeprogress
642 644 with makeprogress(
643 645 _(b'finding outgoing largefiles'),
644 646 unit=_(b'revisions'),
645 647 total=len(missing),
646 648 ) as progress:
647 649 for i, n in enumerate(missing):
648 650 progress.update(i)
649 651 parents = [p for p in repo[n].parents() if p != repo.nullid]
650 652
651 653 with lfstatus(repo, value=False):
652 654 ctx = repo[n]
653 655
654 656 files = set(ctx.files())
655 657 if len(parents) == 2:
656 658 mc = ctx.manifest()
657 659 mp1 = ctx.p1().manifest()
658 660 mp2 = ctx.p2().manifest()
659 661 for f in mp1:
660 662 if f not in mc:
661 663 files.add(f)
662 664 for f in mp2:
663 665 if f not in mc:
664 666 files.add(f)
665 667 for f in mc:
666 668 if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
667 669 files.add(f)
668 670 for fn in files:
669 671 if isstandin(fn) and fn in ctx:
670 672 addfunc(fn, readasstandin(ctx[fn]))
671 673
672 674
673 675 def updatestandinsbymatch(repo, match):
674 676 """Update standins in the working directory according to specified match
675 677
676 678 This returns (possibly modified) ``match`` object to be used for
677 679 subsequent commit process.
678 680 """
679 681
680 682 ui = repo.ui
681 683
682 684 # Case 1: user calls commit with no specific files or
683 685 # include/exclude patterns: refresh and commit all files that
684 686 # are "dirty".
685 687 if match is None or match.always():
686 688 # Spend a bit of time here to get a list of files we know
687 689 # are modified so we can compare only against those.
688 690 # It can cost a lot of time (several seconds)
689 691 # otherwise to update all standins if the largefiles are
690 692 # large.
691 693 lfdirstate = openlfdirstate(ui, repo)
692 694 dirtymatch = matchmod.always()
693 695 unsure, s, mtime_boundary = lfdirstate.status(
694 696 dirtymatch, subrepos=[], ignored=False, clean=False, unknown=False
695 697 )
696 698 modifiedfiles = unsure + s.modified + s.added + s.removed
697 699 lfiles = listlfiles(repo)
698 700 # this only loops through largefiles that exist (not
699 701 # removed/renamed)
700 702 for lfile in lfiles:
701 703 if lfile in modifiedfiles:
702 704 fstandin = standin(lfile)
703 705 if repo.wvfs.exists(fstandin):
704 706 # this handles the case where a rebase is being
705 707 # performed and the working copy is not updated
706 708 # yet.
707 709 if repo.wvfs.exists(lfile):
708 710 updatestandin(repo, lfile, fstandin)
709 711
710 712 return match
711 713
712 714 lfiles = listlfiles(repo)
713 715 match._files = repo._subdirlfs(match.files(), lfiles)
714 716
715 717 # Case 2: user calls commit with specified patterns: refresh
716 718 # any matching big files.
717 719 smatcher = composestandinmatcher(repo, match)
718 720 standins = repo.dirstate.walk(
719 721 smatcher, subrepos=[], unknown=False, ignored=False
720 722 )
721 723
722 724 # No matching big files: get out of the way and pass control to
723 725 # the usual commit() method.
724 726 if not standins:
725 727 return match
726 728
727 729 # Refresh all matching big files. It's possible that the
728 730 # commit will end up failing, in which case the big files will
729 731 # stay refreshed. No harm done: the user modified them and
730 732 # asked to commit them, so sooner or later we're going to
731 733 # refresh the standins. Might as well leave them refreshed.
732 734 lfdirstate = openlfdirstate(ui, repo)
733 735 for fstandin in standins:
734 736 lfile = splitstandin(fstandin)
735 737 if lfdirstate.get_entry(lfile).tracked:
736 738 updatestandin(repo, lfile, fstandin)
737 739
738 740 # Cook up a new matcher that only matches regular files or
739 741 # standins corresponding to the big files requested by the
740 742 # user. Have to modify _files to prevent commit() from
741 743 # complaining "not tracked" for big files.
742 744 match = copy.copy(match)
743 745 origmatchfn = match.matchfn
744 746
745 747 # Check both the list of largefiles and the list of
746 748 # standins because if a largefile was removed, it
747 749 # won't be in the list of largefiles at this point
748 750 match._files += sorted(standins)
749 751
750 752 actualfiles = []
751 753 for f in match._files:
752 754 fstandin = standin(f)
753 755
754 756 # For largefiles, only one of the normal and standin should be
755 757 # committed (except if one of them is a remove). In the case of a
756 758 # standin removal, drop the normal file if it is unknown to dirstate.
757 759 # Thus, skip plain largefile names but keep the standin.
758 760 if f in lfiles or fstandin in standins:
759 761 if not repo.dirstate.get_entry(fstandin).removed:
760 762 if not repo.dirstate.get_entry(f).removed:
761 763 continue
762 764 elif not repo.dirstate.get_entry(f).any_tracked:
763 765 continue
764 766
765 767 actualfiles.append(f)
766 768 match._files = actualfiles
767 769
768 770 def matchfn(f):
769 771 if origmatchfn(f):
770 772 return f not in lfiles
771 773 else:
772 774 return f in standins
773 775
774 776 match.matchfn = matchfn
775 777
776 778 return match
777 779
778 780
779 781 class automatedcommithook:
780 782 """Stateful hook to update standins at the 1st commit of resuming
781 783
782 784 For efficiency, updating standins in the working directory should
783 785 be avoided while automated committing (like rebase, transplant and
784 786 so on), because they should be updated before committing.
785 787
786 788 But the 1st commit of resuming automated committing (e.g. ``rebase
787 789 --continue``) should update them, because largefiles may be
788 790 modified manually.
789 791 """
790 792
791 793 def __init__(self, resuming):
792 794 self.resuming = resuming
793 795
794 796 def __call__(self, repo, match):
795 797 if self.resuming:
796 798 self.resuming = False # avoids updating at subsequent commits
797 799 return updatestandinsbymatch(repo, match)
798 800 else:
799 801 return match
800 802
801 803
802 804 def getstatuswriter(ui, repo, forcibly=None):
803 805 """Return the function to write largefiles specific status out
804 806
805 807 If ``forcibly`` is ``None``, this returns the last element of
806 808 ``repo._lfstatuswriters`` as "default" writer function.
807 809
808 810 Otherwise, this returns the function to always write out (or
809 811 ignore if ``not forcibly``) status.
810 812 """
811 813 if forcibly is None and util.safehasattr(repo, b'_largefilesenabled'):
812 814 return repo._lfstatuswriters[-1]
813 815 else:
814 816 if forcibly:
815 817 return ui.status # forcibly WRITE OUT
816 818 else:
817 819 return lambda *msg, **opts: None # forcibly IGNORE
General Comments 0
You need to be logged in to leave comments. Login now