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